英文:
Linux display command works in terminal, but not in systemd service
问题
我制作了一个网页应用程序来关闭我的计算机屏幕,使用了几种不同的技术,但非常简单:
我有一个HTML/JS前端,它检测按钮点击(屏幕开启/屏幕关闭),然后通过ajax将选项发送到PHP后端。
PHP后端通过TCP端口连接,将选项发送到用Golang编写的程序。
然后,我的Golang程序执行命令来关闭/开启屏幕。
它运行的命令是("xset -display :0 dpms force off")。
我遇到的问题是,当我在终端中运行Golang程序时,命令只能正常工作,但当我将其设置为服务时,命令无法正常工作。
这是Golang代码:
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 closed\n")
}
}
相关的PHP代码:
<?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");
我使用systemd来设置服务,所以在构建程序并将其放置在/usr/bin/之后:
...$ go build screenControl.go
...$ sudo cp screenControl /usr/bin/screenControl
我可以在终端中运行screenControl程序,并在网页应用程序中选择“屏幕关闭”,一切都按预期工作:
...$ screenControl
Launching server
Listening...
New Connection
Message Received: "screensOff"
Turning off screens...
Connection closed
然后,我创建了一个systemd单元文件(/etc/systemd/system/screenControl.service):
[Unit]
Description=Screen control service
[Service]
ExecStart=/usr/bin/screenControl
Restart=on-abort
[Install]
WantedBy=multi-user.target
我启动了服务并进行了检查:
...$ 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...
所以它正在运行,但是当我现在在网页应用程序中选择关闭屏幕时,什么都不会发生...我再次检查了服务状态,它正在接收关闭屏幕的消息,但是命令退出时出现错误:
...
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
这里有什么问题,我如何使该命令作为服务工作?一旦这个工作正常,我希望在机器启动时自动启动该服务,尽管使用systemd,我认为这很简单:
...$ systemctl enable screenControl
任何帮助将是很好的,谢谢
英文:
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 closed\n")
}
}
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 ""
答案1
得分: 1
xset
命令只是X服务器的客户端。它通过检查DISPLAY
环境变量来确定要与哪个X服务器通信,但当您将命令作为系统服务运行时,该变量将不会设置。
即使您确保在运行守护程序时设置了DISPLAY
,它也可能以不同的用户帐户运行,并且默认情况下被拒绝访问显示器。
更好的选择是将守护程序作为用户会话的一部分运行。这将解决身份验证问题(它将作为您运行),并且可以定位显示器(环境变量应该可见)。当您未登录时,守护程序将不会运行,但对于这个特定的用例,这可能并不重要。
您在问题中标记了"Ubuntu",在Ubuntu中,会话仍由Upstart管理。您可以通过在~/.config/upstart
中创建文件来创建新的用户会话作业。文件格式的详细信息可以在init(5)
手册页中找到。
英文:
The xset
command is just a client for the X server. It determines which X server to talk to by checking the DISPLAY
environment variable, which won't be set when you run your command as a system service.
And even if you ensured DISPLAY
was set when running your daemon, it would likely be run as a different user account and denied access to the display by default.
A better option would be to run the your daemon as part of the user session. This will solve the authentication issues (it will be running as you), and the ability to locate the display (the environment variable should be visible). The daemon won't be running when you aren't logged in, but that probably doesn't matter for this particular use case.
You've tagged your question with "Ubuntu", where the session is still managed by Upstart. You can create new user session jobs by creating a file in ~/.config/upstart
. The details of the file format can be found in the init(5)
man page.
答案2
得分: 0
根据David Budworth的评论,修复方法非常简单;因为服务是在root用户下运行的,所以没有设置DISPLAY环境变量。
在Go语言中,可以在使用exec时设置环境变量,如下所示:
//执行关闭屏幕的命令
cmd := exec.Command("xset", "-display", ":0", "dpms", "force", "off")
cmd.Env = []string{"DISPLAY=:0"} // 在执行之前设置显示器
stdout, stderr := cmd.CombinedOutput() // 执行并返回所有输出
根据James Henstridge的回答,我还发现我需要运行xhost +SI:localuser:root
来允许root用户访问X服务器。
您可以在用户登录后通过将以下行添加到/etc/profile
文件的顶部来执行此操作:
xhost +SI:localuser:root > /dev/null 2>&1
或者
您可以在没有用户登录时(显示登录屏幕时)使其工作。
首先,创建目录/opt/scripts
,
然后创建文件/opt/scripts/xhost.sh
,
并使用chmod +x /opt/scripts/xhost.sh
为其赋予可执行权限。
在此文件中只有一行:
xhost +SI:localuser:root > /dev/null 2>&1
然后编辑文件/etc/lightdm/lightdm.conf
(如果存在,则进行编辑)并添加以下行
display-setup-script=/opt/scripts/xhost.sh
因此,我的lightdm.conf
文件如下所示:
[SeatDefaults]
greeter-session=unity-greeter
user-session=ubuntu
display-setup-script=/opt/scripts/xhost.sh
这告诉LightDM(在Ubuntu中运行的显示管理器)在X服务器启动之后但在其他任何操作之前运行脚本/opt/scripts/xhost.sh
,因此root用户立即获得xhost授权!
注意:
display-setup-script在X服务器启动后但用户会话/登录界面运行之前运行。如果需要在X服务器中配置任何特殊设置,请设置此选项。它以root身份运行。如果此命令返回错误代码,则X服务器将停止运行。
来源:https://wiki.ubuntu.com/LightDM
英文:
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论