Bespoke services: application/vncserver

In honour of the “Mugs for Manifests” contest, I thought I would spin out another custom service description I wrote some months ago.

My setup for working from home—key during the last six months of Solaris 10—is to tunnel into Sun’s network via one implementation or another of a virtual private network (VPN). In all cases, the VPN solution runs on Solaris. Although the VPN lets your system participate more or less like a regular host, I find it’s easier to use VNC to remotely present an X11 display from my main workstation, muskoka. But, of course, machine running pre-production bits can fail or be rebooted or be reinstalled regularly, so I wanted the VNC server on my system to always be up: I wanted a VNC service.

What’s distinct about running the VNC server is that it should run as me, with my environment, and not as root with init(1M)’s. svc.startd(1M), while it can run methods according to smf_method(5), doesn’t populate the environment fully in the sense of login(1). So we will need to extract some data from the name service, which is cumbersome to perform in a shell script. We’ll write our method in Perl, which implies

Tip 1: Methods need not be shell scripts.

In fact, the start method and the stop method can be totally separate commands: you could write one in Python, and one can be an executable Java .jar archive, or some even more bizarre combination.

The other trick is that, if VNC fails for some reason, I want to be aggressive about cleaning up its various leftover temporary files. For this purpose, I run the stop method with a different credential—the default of root—than the start method, which is done in our brief manifest by locating the <method_context> element on only the start method.

Tip 2: Methods need not be run with identical method contexts. Credentials, privileges, and the like may all differ from method to method.

Our manifest then looks like:

<?xml version='1.0'?>
<!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
<service_bundle type='manifest' name='export'>
<service name='application/vncserver' type='service' version='0'>
<single_instance/>
<instance name='sch' enabled='true'>
<dependency name='milestone' grouping='require_all' restart_on='none' type='service'>
<service_fmri value='svc:/milestone/multi-user:default'/>
</dependency>
<dependency name='autofs' grouping='require_all' restart_on='none' type='service'>
<service_fmri value='svc:/system/filesystem/autofs:default'/>
</dependency>
<dependency name='nis' grouping='require_all' restart_on='none' type='service'>
<service_fmri value='svc:/network/nis/client:default'/>
</dependency>
<exec_method name='stop' type='method' exec='/home/sch/bin/vncserver_method stop' timeout_seconds='60'/>
<exec_method name='start' type='method' exec='/home/sch/bin/vncserver_method start' timeout_seconds='300'>
<method_context>
<method_credential user='sch' group='staff' />
</method_context>
</exec_method>
</instance>
</service>
</service_bundle>

The dependencies above are needed if you use NFS for home directories and NIS for name services; they could be reduced for less networked setups.

And, for the method, we have a short Perl program. The complete list of environment variables in login(1) would include LOGNAME, PATH, MAIL, and TZ (timezone), and exclude my silly setting of LANG, but most of these will be set up by the shell that the VNC startup script (its analgue to .xinitrc. The various print calls are just to let the service log show a little activity, and could be removed.

!/usr/perl5/bin/perl

require 5.8.3; use strict; use warnings; use locale; my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell, $expire) = getpwuid "$<"; $ENV{USER} = $name; $ENV{HOME} = $dir; $ENV{SHELL} = $shell; $ENV{LANG} = "en_CA"; # Just to create havoc (i.e. expose bugs). #

The stop method is run as root so that it can cleanup.

# if (defined($ARGV[0]) && $ARGV[0] eq "stop") {

ksh and sh specific

print "stop method\n"; system("$ENV{SHELL}", "-c", "/opt/csw/bin/vncserver -kill :1"); if (-S "/tmp/.X11-unix/X1") { unlink("/tmp/.X11-unix/X1"); unlink("/tmp/.X1-lock"); } exit 0; } #

The start method is run with the user's identity.

# print "start method\n"; if (-f "/tmp/.X1-lock") { unlink("/tmp/.X1-lock"); } if (-S "/tmp/.X11-unix/X1") { system("logger -p 1 application/vncserver requires " . "/tmp/.X11-unix/X1 be removed"); exit 0; }

ksh and sh specific

{ exec "$ENV{SHELL}", "-c", "/opt/csw/bin/vncserver -pn -geometry 1600x1200 -depth 24 :1" }; system("logger -p 1 application/vncserver can't exec /opt/csw/bin/vncserver"); exit 1;

And now we have always-on VNC service for the regular telecommuter:

$ svcs -p vncserver
STATE          STIME    FMRI
online         13:01:01 svc:/application/vncserver:sch
13:01:00   100577 Xvnc
13:01:17   100625 xwrits
13:01:17   100626 ctrun
13:01:17   100632 xautolock
13:11:18   102348 xlock
$ uptime
12:00pm  up 23 hr(s),  4 users,  load average: 0.04, 0.07, 0.07

Exercises

  1. Remove the hard coded display numbering (“:1”, “X1”, etc.).
  2. Make the resolution, display depth, RGB encoding, and other standard options into properties.

[ T: Solaris smf ]