用智能手机控制你的 Linux 系统

作者:Jamie Popkin

手机技术最近取得了长足的进步。个人电脑和手持设备之间的差距正在缩小。我一直听到“PC 的末日”这种说法,并且这句话可能有些道理。但是,我相信我们中的许多人将继续需要访问更大、更强大的计算机,这些计算机太大而无法放入口袋。对我来说,两全其美的方法是从我的手机完全控制一台更大的计算机。

许多新型智能手机都内置了先进的 Web 浏览器。借助这项技术,您可以访问配置为在几乎任何计算机上运行任何命令的界面。在 Linux 机器上运行 Web 服务器非常简单。如果您采取适当的安全措施,您可以快速构建专门为手持设备设计的 Web 界面。

安全

本文展示的方法是使用用户帐户在系统上运行命令。当然,这样做存在安全隐患,但采取适当的预防措施后,可以使其相当安全。

该系统将依赖 Wi-Fi。这在处理手持设备时很有意义,因此请为您的 Wi-Fi 路由器配置密码。想要连接到本地 Intranet 的用户必须先在其设备上输入密码才能看到任何内容。大多数设备都会记住凭据,并在进入范围后自动连接。

为了最大限度地降低安全漏洞事件的风险,我们还应创建权限最小的用户帐户。即使界面仅公开“安全”命令,这也是一个很好的安全措施。

设置

如果尚未安装,请从您的发行版存储库安装以下内容:Apache2、apache2-suexec 和 libapache2-mod-perl2。

第一个软件包是 Web 服务器。如果安装后没有自动启动,请运行命令

/etc/init.d/apache2 start

第二个软件包允许您使用特定系统用户的凭据运行 Web 服务器。安装后,您需要以 root 身份发出以下命令来启用 apache 模块

a2enmod suexec

这里介绍的一些示例需要 Perl CGI 互操作性。最后一个软件包是为此需要的。

现在,您需要配置 Apache 以作为您不太信任的用户运行。在我们的家庭 Linux 机器上,我为所有孩子创建了一个帐户。用户名是“saturn”。此帐户可以执行诸如播放音乐和观看视频之类的操作。但是,它不属于任何可以删除或更改重要内容的组。因此,让我们以这个帐户为例。

编辑您的 apache 配置,并将以下行添加到默认的 VirtualHost (*:80) 或您想要使用的 VirtualHost

SuexecUserGroup saturn saturn

Apache 以 root 身份运行,因此它有能力以任何用户身份运行脚本。上面的行告诉 Apache 以用户 saturn 和组 saturn 身份运行。

现在,使用此命令以 root 身份重启 Apache

/etc/init.d/apache2 restart

Web 服务现在以用户 saturn 身份运行。

最简单的例子

从命令行播放声音文件非常简单,并且这是展示此设置的简单性的一个好方法——一个按钮对应一个动作。

我正在使用传统的 Web 技术栈:HTML、CSS、JavaScript 和 CGI。CGI 部分可以使用多种不同的语言来实现。为了简单起见,第一个示例使用 shell 脚本。

在根 Web 目录中创建一个 index.html 文件。对于许多系统,这位于 /var/www/ 中。有些系统使用 /var/www/html/。在此文件中,添加一个调用名为 playQuack() 的 JavaScript 函数的按钮

<button id="quack-button" onclick="playQuack()">Quack</button>

playQuack() 函数的 JavaScript 代码在 bonkers.js 中。整个 index.html 文件如下所示

<html>
  <head>
    <title>Bonkers</title>
    <meta name="viewport"
          content="initial-scale=1.0; user-scalable=no"/>
    <link rel="stylesheet" type="text/css" href="default.css" />
    <script type="text/javascript" src="bonkers.js"></script>
  </head>

  <body>
    <button id="quack-button" onclick="playQuack()">Quack</button>
  </body>
</html>

一些值得一提的附加内容在元标记中。这告诉智能手机不要缩放页面的内容。如果没有这个,按钮在屏幕上会非常小。

这是我的 default.css 文件。它定义了背景颜色并指定了按钮的外观

html, body {
  background-color: #1E1E26;
}

button#quack-button {
  position: absolute;
  top: 20%;
  width: 70%;
  left: 15%;
  padding: 5px;
  border-width: 3px;
  color: #BFBFBF;
  font-size: 34px;
  font-weight: 800;
  border-color: #9C9C9C;
  background-image: -webkit-gradient(linear, left top
      left bottom, from(#BF5A34), to(#463630));
      -webkit-border-radius: 10px;
}

许多移动浏览器开始支持 WebKit CSS。这令人兴奋,因为几行 WebKit 代码可以做一些非常花哨的事情。最后一个花括号之前的最后两行告诉按钮具有圆角和颜色渐变背景。

现在您有了一个外观漂亮的按钮。将手机上的浏览器指向 Linux 计算机的 IP 地址。您应该会看到类似于图 1 的内容。

Controlling Your Linux System with a Smartphone

图 1. iPhone 上显示的简单按钮

接下来,让我们让按钮真正做些事情。在根 Web 目录中创建 bonkers.js 文件,然后输入以下内容

var myDomain = document.domain;
var cgiURL   = "http://" + myDomain + "/cgi-bin/bonkers.cgi";
var xmlRequest;

function playQuack() {
  xmlRequest = new XMLHttpRequest();
  xmlRequest.open("GET", cgiURL, true);
  xmlRequest.send(null);
}

这是构成客户端进程的 JavaScript 代码。它创建一个 URL,该 URL 本质上是在您的 Linux 机器上运行 CGI 脚本。在此示例中,您实际上并不关心 CGI 脚本的返回值。

信不信由你,最难的部分已经完成了。CGI 脚本非常简单易懂——特别是对于习惯使用命令行的人来说。

所有 CGI 脚本都必须位于 cgi-bin 目录中。这通常位于 /var/www/cgi-bin 或 /usr/lib/cgi-bin 中,并且也可以在 Apache 中配置。

这是 CGI 脚本 bonkers.cgi

#!/bin/bash

mplayer ~/quack.wav &

就这些。这是一个 Bash shell 脚本。shell 路径的引用在顶部。下面是运行 MPlayer 的命令,它会播放令人讨厌的嘎嘎声。您基本上可以在此处放置任何 shell 命令。

就是这样。任何拥有智能手机和 Wi-Fi 密码的人都可以让计算机发出嘎嘎声。现在是时候做一些更有用的事情了。

大坏蛋

我和我的妻子有四个孩子。他们都在争夺我们家 Linux 机器的使用时间。这台名为 Saturn 的计算机始终以帐户“saturn”登录。所有孩子都使用此帐户,并且要么在看电视、视频、YouTube,要么在听音乐,要么在 Internet 上玩 Flash 游戏。我厌倦了在晚餐时间把孩子们从电脑上赶走。这就是创建“big-meanie”界面的原因。

下一个应用程序扩展了前面的示例。我选择使用 Perl 作为 CGI 语言,主要是因为我是一位长期的 Perl 用户和粉丝。在以下示例中,我省略了 HTML 和 CSS 代码,因为它与第一个示例中的代码没有显着差异。

主要区别在于此示例有三个按钮:一个用于启动五分钟倒计时,直到所有乐趣结束。另一个是停止倒计时,以防我改变主意。最后一个是我真的下定决心并且想要立即终止所有适用的程序。

图 2 显示了“大坏蛋”的布局。

Controlling Your Linux System with a Smartphone

图 2. iPhone 上显示的“大坏蛋”

第一个按钮给孩子们五分钟倒计时,让他们离开电脑。我认为在桌面上当前窗口上方弹出视觉指示器是合适的。您需要设置一些 shell 变量才能输出到本地显示器:XAUTHORITY、HOME 和 DISPLAY。以下 Perl 命令可以完成此操作

$ENV{'XAUTHORITY'} = '/home/saturn/.Xauthority';
$ENV{'HOME'} = '/home/saturn';
$ENV{'DISPLAY'} = ':0';

这允许窗口在本地显示器上打开,即使该命令是远程发起的。

接下来要注意的是,您有多个功能要执行,因此您需要告知脚本要执行哪个操作(即,按下了哪个按钮)。由于您在此处处理的是 Web URL,因此将您的参数附加到 URL 是实现此目的的最简单方法。使用 ? 符号告诉您的脚本,以下文本是参数。该参数将是要执行的操作。

这是“大坏蛋”的完整 JavaScript 文件

var myDomain = document.domain; // Grab current domain.
var cgiURL  = "http://" + myDomain + "/cgi-bin/big-meanie.cgi";
var xmlRequest;

// This is the logic for the "5 Minute Warning" button
function warn5() {
  xmlRequest = new XMLHttpRequest();
  // Add the 5min variable to the URL
  xmlRequest.open("GET",cgiURL + "?5min", true);
  xmlRequest.send(null);
}

// This is the logic for the "Cancel Countdown" button
function cancel() {
  xmlRequest = new XMLHttpRequest();
  // Add the cancel variable to the URL
  xmlRequest.open("GET",cgiURL + "?cancel", true);
  xmlRequest.send(null);
}

// This is the logic for the "Get Off Now" button
function offnow() {
  xmlRequest = new XMLHttpRequest();
  // Add the off-now variable to the URL
  xmlRequest.open("GET",cgiURL + "?off-now", true);
  xmlRequest.send(null);
}

每个按钮都有自己的 JavaScript 函数。CGI 脚本的 URL 在每个函数中都附加了相应的变量。然后,该变量通过 xmlRequest.send() 函数传递给 CGI 脚本。

到目前为止,所有操作都发生在手机浏览器上。现在,让我们深入研究服务器上的脚本 big-meanie.pl。

在介绍服务器端脚本(清单 1)之前,还有最后一件事要提及。当发出 URL 请求时,Internet 浏览器期望站点做出响应。如果没有响应,浏览器将挂起。您可以通过创建一个自动以空消息响应的辅助线程来解决此问题。当服务器以这种方式执行命令时,浏览器不会卡住,这对于您接下来要做的事情很重要。

清单 1. 服务器端脚本

#!/usr/bin/perl

# Grab the URL variable
$variable = $ARGV[0];

# Set some important environment variables.
$ENV{'XAUTHORITY'} = '/home/saturn/.Xauthority';
$ENV{'DISPLAY'}    = ':0';
$ENV{'HOME'}       = '/home/saturn';

# if the 5 minutes warning button is pushed
if ($variable =~ /^5min$/) {
    # Lets create a child process to deal with the notifications
    defined(my $childpid = fork);

    if ($childpid) { # If a child pid exists... this is the parent
        # Send response so the browser is happy
        print "Content-type: text/html\n\n";

        # Show a popup warning message
        # displaying 5 minutes remaining.
        `zenity --warning --text='5 minutes left to play'`;
    } else { # Otherwise it's the child
        # Print the amount of time left with a subtle gnome
        # notification message.
        sleep(60);
        `notify-send '4 minutes left to play'`;
        sleep(60);
        `notify-send '3 minutes left to play'`;
        sleep(60);
        `notify-send '2 minutes left to play'`;
        sleep(60);
        `notify-send '1 minutes left to play'`;
        sleep(60);

        # We are now out of time.
        # Let's close all the fun applications
        `/usr/bin/tvtime-command quit`; # Close the TV
        `pkill mplayer`;                # Close mplayer
        `pkill totem`;                  # Close Totem movie player
        `pkill rhythmbox`;              # Close the music player
        `pkill firefox`;                # Close the web browser
    }
}

# If the Cancel Countdown button has been pushed.
if ($variable =~ /^cancel$/) {
    defined(my $childpid = fork);

    if ($childpid) { # If parent
        print "Content-type: text/html\n\n";
    } else {
       `pkill big-meanie`;
    }
}

# If the Get Off Now button has been pushed.
if ($variable =~ /^off-now$/) {
    defined(my $childpid = fork);

    if ($childpid) { # If parent
        print "Content-type: text/html\n\n";
    } else {
        `/usr/bin/tvtime-command quit`; # Close the TV
        `pkill mplayer`;                # Close mplayer
        `pkill totem`;                  # Close Totem movie player
        `pkill rhythmbox`;              # Close the music player
        `pkill firefox`;                # Close the web browser

         # Just because we can... Send out a tweet that
         # someone has been kicked off the computer
         `curl -u <twitterUser>:<twitterPassword> -d
            status='Someone has been rudely kicked off the computer.'
            http://twitter.com/statuses/update.xml > /dev/null`;
    }
}

来自 URL 的 action 变量通过 $ARGV[0] 数组获取。后面的 if 语句测试指定了哪个变量,因此,要执行哪个操作。第一个动作,5 分钟, 通过 fork CGI 脚本(创建进程的副本)执行五分钟警告。fork 的“父”分支执行 if 语句的 if 部分,而“子”分支执行 else 部分。父级向浏览器发送一个空的 HTML 响应,以便客户端不会挂起,并使用 Zenity 弹出一个五分钟警告消息框。Zenity 提供了一个简单的 GUI 界面,可以从脚本访问它,并且它应该默认安装在大多数 GNOME 桌面环境中。KDE 用户有替代方案,例如 kdialog 或 whiptail。将弹出对话框放在与倒计时通知分开的线程中的优点是,如果当前用户未单击 Zenity 窗口上的“确定”按钮,脚本不会停顿。

fork 的子部分执行实际的五分钟倒计时。我想在这里表现得人性化一点,并提供关于用户被踢出之前还剩多少时间的细微通知。这允许年龄较大的孩子保存他们的会话或工作。我的小孩子只需要逐渐接受停止的想法。

脚本的子部分在对话框出现后休眠 60 秒。然后,一旦子进程醒来,就会向桌面发送一个简单的通知。它以标准 GNOME 通知的形式出现在屏幕的右上角。图 3 显示了它对用户的显示方式。此过程重复进行,直到倒计时完成。接下来,“大坏蛋”的 meanie 部分开始启动,并向孩子们可能正在享受的任何有趣的程序发送 kill 信号。

Controlling Your Linux System with a Smartphone

图 3. 桌面上显示剩余时间的 GNOME 通知

我使用了 GNOME 常用的 notify-send 命令。它包含在 libnotify-bin 软件包中,并且应该在大多数发行版存储库中。KDE 用户可以使用带有 --passivepopup 标志的 kdialog 来代替 notify-send。

第二个 if 语句用于取消倒计时按钮。它向所有 big-meanie 实例发送 kill 信号,基本上会杀死自身和所有休眠实例,这将杀死任何先前启动的五分钟等待子进程,这些子进程正在等待关闭事物。

最后一个 if 语句不需要太多解释。它立即将用户踢出,并且与第一个语句几乎相同,只是不包括倒计时逻辑。您可能已经注意到代码末尾有趣的小补充:big-meanie 向 Twitter 发送状态更新,声明有人已被踢出计算机。对于 big-meanie 来说,向全世界炫耀某人的乐趣已被终止似乎是合适的。它还可以作为一种安全功能,告知我是否有一个孩子发现了该程序,并且可能以任何不友好的方式在兄弟姐妹身上使用它。

其他可能性

我构建的最常用的应用程序是电视遥控器。我们使用出色的 tvtime 程序来观看所有电视节目。tvtime 软件包包括tvtime-command命令,它允许您控制正在运行的 tvtime 实例的每一个可以想象到的功能。例如,tvtime-command chan-up将频道向上调整一个增量。完整的文档可以在 tvtime 手册页中找到。

我使用了前面描述的所有相同技术,并构建了一个如图 4 所示的完整功能的遥控器。

Controlling Your Linux System with a Smartphone

图 4. Palm Pre 上显示的遥控器应用程序

现在,朋友和家人可以从他们自己的智能手机的舒适性中控制电视。不再有人抱怨使用我们巨大的蓝牙键盘作为遥控器了。孩子们只需拿起最近的 iPod Touch、Palm Pre 或其他任何东西即可。

结论

智能手机技术正在飞速发展。无需修改手机,您可以通过内置浏览器访问远程计算机上的几乎所有可能的功能。Linux 用户尤其幸运。将 Linux 计算机变成手机的后端并不需要太多的努力。希望本文能够启发您提出自己独特而有趣的用途。

Jamie Popkin 与他的妻子和四个孩子住在不列颠哥伦比亚省兰茨维尔。他是一名顾问,专门从事 Web 上的地理数据描绘。最近,他开始利用现代 Web/HTML5 技术为智能手机进行开发。可以通过 Twitter (@jamiepopkin) 或电子邮件 (popkinj@littleearth.ca) 与他联系。

加载 Disqus 评论