Linux显示命令在终端中工作,但在systemd服务中不工作
我制作了一个web应用程序来关闭我的电脑屏幕,有几种不同的技术,但它相当简单: 我有一个html/js前端,它检测到一个按钮点击(屏幕打开/关闭),通过ajax将选项发送到PHP后端 php然后通过tcp端口连接,将选项发送到用golang编写的程序 然后我的golang程序执行命令关闭/打开屏幕。 它运行的命令是(“xset-display:0 dpms force off”) 我遇到的问题是,该命令仅在终端运行golang程序时有效,但当我将其设置为服务时,该命令不起作用 这是golang代码:Linux显示命令在终端中工作,但在systemd服务中不工作,linux,ubuntu,service,go,systemd,Linux,Ubuntu,Service,Go,Systemd,我制作了一个web应用程序来关闭我的电脑屏幕,有几种不同的技术,但它相当简单: 我有一个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");
我可以在终端中运行screenControl程序,在web应用程序中选择“screens off”(屏幕关闭),一切正常:
...$ screenControl
Launching server
Listening...
New Connection
Message Received: "screensOff"
Turning off screens...
Connection closed
然后我创建了一个systemd单元文件(/etc/systemd/system/screenControl.service):
我启动了服务并进行了检查:
...$ 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...
所以它正在运行,但当我现在在web应用程序中选择关闭屏幕时,什么都没有发生。。。我再次检查了服务状态,它收到关闭屏幕的消息,但命令退出时出现错误:
...
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
任何帮助都会很好,谢谢:)
编辑
让golang程序向我显示xset命令的stderr后,我现在也有错误消息:
xset: unable to open display ""
xset
命令只是X服务器的客户端。它通过检查DISPLAY
环境变量来确定与哪个X服务器对话,当您将命令作为系统服务运行时,不会设置该环境变量
即使您确保在运行守护程序时设置了DISPLAY
,它也可能作为不同的用户帐户运行,并且默认情况下拒绝访问显示
更好的选择是将守护进程作为用户会话的一部分运行。这将解决身份验证问题(它将在您运行时运行),以及定位显示的能力(环境变量应该是可见的)。当您未登录时,守护进程将不会运行,但这可能与此特定用例无关
您已经用“Ubuntu”标记了您的问题,其中会话仍然由管理员管理。您可以通过在
~/.config/upstart
中创建文件来创建新的用户会话作业。文件格式的详细信息可以在中找到。根据David Budworth的评论,修复非常简单;由于服务在根目录下运行,因此未设置DISPLAY环境变量
在go中,您可以在使用exec时设置环境变量,如下所示:
//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
从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授权
注意:
显示设置脚本在X服务器启动后但在用户会话/问候器运行之前运行。如果需要在X服务器中配置任何特殊内容,请设置此选项。它以root用户身份运行。如果此命令返回错误代码,X服务器将停止。
来源:xwindows不允许当前会话所有者以外的用户发送命令。在调用您的服务之前,请尝试使用shell中的“xhost+”。可能是root被阻止关闭屏幕。但当您从shell运行命令时,它会起作用,因为您拥有sessionI停止了服务,尝试了“xhost+”表示“访问控制已禁用,客户端可以从任何主机连接”,然后再次启动该服务,但它仍然获得“退出状态1”从xset命令中:/您是否生成了实际的错误消息,以使用退出代码1执行go?我使用go,并让它也显示stderr输出:(退出状态1:xset:not open display“”)我想知道它是否像您所说的那样,并且root被阻止,虽然我可以从根终端运行golang程序,但它可以运行。。但这是否因为我从桌面用户会话启动了根会话(通过“sudo-I”)而起作用?这比我想象的要深刻,有趣的东西:)当它说“显示”时,意味着它不知道是哪个显示。当您从go执行命令行应用程序时,您可以传递环境,尝试使用DISPLAY=“:0”执行该操作。我以为Upstart在15.04中已被systemd替换?(我在15.10上)但是,除非我在用户会话中运行“xhost+”,否则它确实无法工作。。。是否可以授权根用户也可以访问显示?因此,无论是谁登录,它仍将工作。这样做会有什么问题吗?我注意到我可以运行
xhost+SI:localuser:root
来授权root用户,所以我把它放在了bo上
xhost +SI:localuser:root > /dev/null 2>&1
[SeatDefaults]
greeter-session=unity-greeter
user-session=ubuntu
display-setup-script=/opt/scripts/xhost.sh