Golang cmd.Output() of exec.Command() throws error in systemd service (ubuntu)

Viewed 617

I have a go binary that gets display information using cmd := exec.Command("xdpyinfo") then calling cmd.Output(). The program runs as expected in the terminal. However, when I run this as a systemd service there's no output from cmd.Output(). I'm program is running as a service, but calling cmd.Output() is returning an error.

I've tried to make this as easy to reproduce as possible:

I have a executable go binary named test at /home/myusername/Projects/test The binary was built using go build from a single main.go file containing the following code:

package main

import (
        "fmt"
        "log"
        "log/syslog"
        "os/exec"
)

func main() {
        logwriter, e := syslog.New(syslog.LOG_NOTICE, "testprog")
        if e == nil {
                log.SetOutput(logwriter)
        }

        cmd := exec.Command("xdpyinfo")

        out, err := cmd.Output()
        if err != nil {
                log.Print(fmt.Errorf("ERROR: %v", err))
        }

        log.Print(string(out))
}

The logging is just for test purposes as the real code is part of a larger app.

When I run the test binary in the terminal I see the expected output from xdpyinfo in the logs. However, when try to run the same binary in a service it outputs ERROR: exit status 1.

Steps I took to run the service:

  1. Create a unit service file at /etc/systemd/system/TestProg.service:
[Unit]
Description=Test service
ConditionPathExists=/home/myusername/Projects/test/test
After=network.target

[Service]
Type=simple

Restart=on-failure
RestartSec=10
startLimitIntervalSec=60

WorkingDirectory=/home/myusername/Projects/test
ExecStart=/home/myusername/Projects/test/test

[Install]
WantedBy=multi-user.target
  1. Enable the service:

sudo systemctl enable TestProg.service

  1. Start the service:

sudo systemctl start TestProg.service

I've also tried locating the unit service file at /lib/systemd/system/TestProg.service and adding the User=username under Type=simple.

I've tried several different configurations to try and get this working. I've also tried setting the user using SysProcAttr (with actual uid and gid):

cmd := exec.Command("xdpyinfo")

cmd.SysProcAttr = &syscall.SysProcAttr{
  Credential: &syscall.Credential{
    Uid:         uint32(uid),
    Gid:         uint32(gid),
    NoSetGroups: true,
  },
}

out, err := cmd.Output()
if err != nil {
        log.Print(fmt.Errorf("ERROR: %v", err))
}

I'm now starting to think I'm missing something more fundamental.

1 Answers

You have to run it in your user session (which also started you X11 session):

systemd-run --user -t  xdpyinfo |head
Running as unit: run-u1614.service
Press ^] three times within 1s to disconnect TTY.
name of display:    :0
version number:    11.0
vendor string:    The X.Org Foundation

You exampe without golang:

sudo systemd-run -t  xdpyinfo 
Running as unit: run-u2876.service
Press ^] three times within 1s to disconnect TTY.
/usr/bin/xdpyinfo:  unable to open display "".
Related