Skip to content
Advertisement

Linux display command works in terminal, but not in systemd service

I made a web app to turn off my computer’s screens, there are a few different technologies but it’s fairly simple:

I have a html/js frontend that detects a button click (Screens On / Screens Off) which sends the option to the PHP backend via ajax

The php then connects over a tcp port, sending the option to a program written in golang

Then my golang program executes the command to turn off/on the screens. The command it runs is (“xset -display :0 dpms force off”)

The problem I’m having is that the command only works when im running the golang program in the terminal, but when i set it up as a service the command wont work.

This is the golang code:

package main

import (
    "os/exec"
    "net"
    "fmt"
    "bufio"
)

func main() {
    fmt.Println("Launching server")

    ln, _ := net.Listen("tcp", ":7777")
    fmt.Println("Listening...n")

    for {
        // accept connection on port
        conn, _ := ln.Accept()
        fmt.Println("New connection")

        // listen for message ending in n
        message, _ := bufio.NewReader(conn).ReadString('n')
        rec := string(message)

        // remove trailing n
        rec = rec[:len(rec)-1]

        fmt.Println("Message Received: ", """+rec+""")

        returnMessage := "fail"

        if (rec == "screensOff") {
            fmt.Println("Turning off screens...")

            //execute screens off command
            cmd := exec.Command("xset", "-display", ":0", "dpms", "force", "off")
            stdout, err := cmd.Output()

            if err != nil {
                fmt.Println(err.Error())
            } else {
                fmt.Println(string(stdout))
                returnMessage = "done"
            }
        } else if (rec == "screensOn") {
            fmt.Println("Turning on screens...");

            //execute screens on command
            cmd := exec.Command("xset", "-display", ":0", "dpms", "force", "on")

            stdout, err := cmd.Output()
            if err != nil {
                fmt.Println(err.Error())
            } else {
                fmt.Println(string(stdout))
                returnMessage = "done"
            }
            returnMessage = "done"
        } 

        conn.Write([]byte(returnMessage + "n"))

        conn.Close()
        fmt.Println("Connection closedn")
    }
}

And relevant PHP code:

<?php
function sendServiceMessage($message) {
    $host = "localhost";
    $port = 7777;
    $timeout = 30;

    // connect to service
    $socket = fsockopen($host, $port, $errnum, $errstr, $timeout);
    if (!is_resource($socket)) {
        exit("connection fail: ".$errnum." ".$errstr);
    }
    else {
        // send message
        fputs($socket, $message."n");

        // receive return message
        $recieved = "";
        while (!feof($socket)) {
            $recieved .= fgets ($socket, 1024);
        }
    }

    // close connection
    fclose($socket);
    if ($recieved == "done") {
        return true;
    }
    return false;   
}

sendServiceMessage("screensOff");

I used systemd to set up the service, so after building the program and placing it in /usr/bin/

...$ go build screenControl.go
...$ sudo cp screenControl /usr/bin/screenControl

I can run the screenControl program in the terminal, and select “screens off” in the web app and it all works as expected:

...$ screenControl
Launching server
Listening...

New Connection
Message Received:  "screensOff"
Turning off screens...

Connection closed

I then created a systemd unit file (/etc/systemd/system/screenControl.service):

[Unit]
Description=Screen control service

[Service]
ExecStart=/usr/bin/screenControl
Restart=on-abort

[Install]
WantedBy=multi-user.target

I started the service and checked it:

...$ systemctl start screenControl
...$ systemctl status screenControl
● screenControl.service - Screen control service
   Loaded: loaded (/etc/systemd/system/screenControl.service; disabled; vendor preset: enabled)
   Active: active (running) since Sun 2015-12-13 22:31:54 GMT; 6s ago
 Main PID: 19871 (screenControl)
   CGroup: /system.slice/screenControl.service
           └─19871 /usr/bin/screenControl

Dec 13 22:31:54 User systemd[1]: Started Screen control service.
Dec 13 22:31:54 User screenControl[19871]: Launching server
Dec 13 22:31:54 User screenControl[19871]: Listening...

So it’s running, but then when I select screens off in the web app now, nothing happens… I checked the service status again and it is receiving the message to turn the screens off but the command is exiting with an error:

...
Dec 13 22:31:54 User screenControlTest[19871]: Launching server
Dec 13 22:31:54 User screenControlTest[19871]: Listening...
Dec 13 22:32:25 User screenControlTest[19871]: New connection
Dec 13 22:32:25 User screenControlTest[19871]: Message Received:  "screensOff"
Dec 13 22:32:25 User screenControlTest[19871]: Turning off screens...
Dec 13 22:32:25 User screenControlTest[19871]: exit status 1
Dec 13 22:32:25 User screenControlTest[19871]: Connection closed

What’s the problem here and how can I get that command to work as a service? Once this is working I want to have the service start automatically when the machine turns on, although with systemd I think that’s as simple as:

...$ systemctl enable screenControl

Any help would be great, thank you 🙂

Edit

After having the golang program show me the stderr of the xset command, I now also have the error message:

xset:  unable to open display ""

Advertisement

Answer

As per David Budworth’s comment, the fix was exceedingly simple; as the service was running under root, it didn’t have the DISPLAY environment variable set.

In go you can set the environment variables when using exec like so:

//execute screens off command
cmd := exec.Command("xset", "-display", ":0", "dpms", "force", "off")
cmd.Env = []string{"DISPLAY=:0"} // set the display before executing
stdout, stderr := cmd.CombinedOutput() //execute and return all output

And from James Henstridge answer I found I also needed to run xhost +SI:localuser:root to allow the root user access to the X server.

You can do this for users after they’ve logged in by adding this line to the top of the /etc/profile file

xhost +SI:localuser:root > /dev/null 2>&1

OR

You can get it to work even when no user is logged in (when the login screen is showing)

First I created the directory /opt/scripts then created the file /opt/scripts/xhost.sh and gave it executable permissions with chmod +x /opt/scripts/xhost.sh

In this file is just the one line:

xhost +SI:localuser:root > /dev/null 2>&1

Then edit the file /etc/lightdm/lightdm.conf (I had to create it, but edit it if it’s there) and add the line display-setup-script=/opt/scripts/xhost.sh

So my lightdm.conf file looks like this:

[SeatDefaults]
greeter-session=unity-greeter
user-session=ubuntu
display-setup-script=/opt/scripts/xhost.sh

This tells LightDM (the display manager running in Ubuntu) to run the script /opt/scripts/xhost.sh after the X server is started but before anything else, therefore root is given the xhost authorization straightaway!

note:

display-setup-script is run after the X server starts but before the user session / greeter is run. Set this if you need to configure anything special in the X server. It is run as root. If this command returns an error code the X server is stopped. Source: https://wiki.ubuntu.com/LightDM

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement