Virtual Plan 9 Logo


Provide a step-by-step explanation for converting a non-networked, stand-alone Plan 9 configuration into an Internetworking cpu and auth server suitable for logins from other Plan 9 systems as well as UNIX systems that support the drawterm package.

Creative Commons License Except where otherwise noted, content in this document is licensed under the standard Creative Commons Attribution–ShareAlike 4.0 International License.
Source code (scripts) are licensed under GPLv2.

Prev: Install Stand-Alone Terminal

Up: Top

Next: Top

Virtual Plan 9 Cookbook
Section 4: Convert Terminal Into Server

Click on the lines following to jump down to their content:

Subsection 4-0: Introduction

Subsection 4-1: A Quick Look at the Name Space

Step 4-1-1: Our First ls Command

Step 4-1-2: The / Directory

Step 4-1-3: The /root Directory

Step 4-1-4: The /n Directory

Step 4-1-5: Run the 9fat: Command

Subsection 4-2: Preliminary Reconfiguration

Step 4-2-1: Modify plan9.ini vgasize

Step 4-2-2: Modify /adm/timezone/local

Step 4-2-3: Reboot Prior to Major Reconfiguration

Subsection 4-3: Install Scripts From USB

Step 4-3-1: Prepare the USB-stick

Step 4-3-2: Enable Virtual USB Access

Step 4-3-3: Attach USB-stick to Plan 9 Server

Step 4-3-4: Copy Files (with env and keyboard Tutorials)

Subsection 4-4: Run term2cpu to Reconfigure

Step 4-4-1: Set the Reconfiguration Parameters

Step 4-4-2: Reconfigure the Server

Step 4-4-3: Shut Down the Reconfigured Terminal

Subsection 4-5: Finish the Reconfiguration

Step 4-5-1: Boot Up the Server

Step 4-5-2: Set Up Access and New Account

Subsection 4-6: Connect Via Drawterm

Subsection 4-0: Introduction

This process will implement the changes discussed in Configuring a Standalone CPU Server at the Bell Labs Wiki but that paper is not really a step-by-step recipe. Instead, this section provides that methodology by capitalizing on the work of maht, the contributor of the make_cpuauth rc script referenced at the end of the paper. However, that script required significant revision and should not be used as it currently stands. The updated version, term2cpu is contained in this cookbook's directory along with a few related others.

Ideally these scripts would be shipped as part of the ISO; instead, we need to install them into a Plan 9 system that has insufficient securitization and so no active networking. A vfat-formatted USB-stick fulfills this medium of transfer requirement without much convolution.

Before we get to that, we will finish the Installation Instructions regarding the Changing the Screen Resolution and Setting Up Correct Timezone sections.

Subsection 4-1: A Quick Look at the Name Space

After you have absorbed how to work with the rio GUI (as you were directed at the conclusion of the preceding section), delete all the open windows on the desktop and open a new window as large as your GUI will allow. It should look pretty much like this:

Before we run that {9fat:} command in the Installation Instructions, it would be good to better understand what it will accomplish.

As an experienced GNU/Linux sysadmin, you understand the / directory as the mount point of the system's root filesystem as specified in the GRUB configuration, and that other filesystems can be mounted into mount points within the hierarchy of the / tree, thus becoming logical extensions of the root filesystem. You are also aware that some mounted filesystems, like /proc, aren't disk-resident at all—they are only fabrications of the kernel that provide a simplified interface between kernel space and user space. In the universe of UNIX implementations, there is a single file space for the entire system shared by all processes within that system, and it is managed by the root users.

Plan 9, on the other hand, allows all processes to manage their view of the file space (with some security and integrity restrictions, of course). This name space is established by the system for the user when login occurs, much like the UNIX environment variables are for UNIX users when they log in. But the Plan 9 user will redefine the name space as needed, and subordinate processes or process groups can modify their name spaces independently of their ancestors' name spaces; again, much like UNIX environment variables.

Do not overlook the all-encompassing role of the Plan 9 name space since just about every interface within the system involves reading from and writing to entities defined within the name space—Plan 9 contains no ioctl interfaces. Yes, even when interacting with resources on remote systems—a Plan 9 application usually has no idea whether the resources it is interacting with are local or remote, the interfaces are identical once the name space has been modified to use the desired resources, and the application may not be involved with that all; i.e., it may be designed to just use what the name space already contains.

By the way, the traditional UNIX access permissions (wrxwrxwrx) are alive and well in Plan 9's name spaces, thus they are expanded to control access to everything, local and remote, including the Plan 9 approach to ioctl. However, the UNIX concept of a superuser did not make the cut—all Plan 9 users are subject to the access bits.

The ns command is used to display the name space somewhat like the UNIX df command displays the organization of mounted filesystems. Without any arguments, it shows the commands needed to reconstruct the current name space of the process, in the order they need to be executed:

As this is a quick introduction, we will gloss over a lot of important details to focus on the main concepts to get in mind at this time.

The first main concept involves those bind and mount options that initially don't look so different. However, they are associated with a very non-UNIX concept the Plan 9 developers call union directories. A union can be formed of two or more directories that will then be used as alternative search paths.

The second main concept is those #_ entities, which denote kernel drivers that are documented in Section 3 of the man pages. The intro(3) page provides an overview of this class of widgets and a {cat /dev/drivers} command will list the device codes of which the system is aware (a tip of the hat to "dante" <> and ). The following table lists all the non-introductory man pages in section 3 in lexical order of their device codes and includes the device code (with # prefix and any qualification syntax associated with it), UTF-8 in hex, a link to its man page, and the title from the man page:




Plug 'n' Play ISA and PCI Interfaces




the root file system [note bind '#/' is illegal—only the kernel can access this driver directly]




SoundBlaster or ESS1688 audio controller




Ethernet bridge and IPv4 tunnel




SSL record layer




flash memory



ip(3) [aka esp, gre, icmp, icmpv6, ipmux, rudp, tcp, and udp]

network protocols over IP




kernel profiling




parallel port interface for PC's




attach to 9P servers




Advanced Power Management 1.2 BIOS interface




architecture–specific information and control




storage device interface




AHCI (Advanced Host Controller Interface) SATA (Serial ATA) storage device drivers




ATA–over–Ethernet (AoE) storage device interface




network link simulation




TLS1 and SSL3 record layer




console, clocks, process/process group ids, user, null, reboot, etc.




dups of open files




environment variables




floppy diskette interface




long lived memory segments




screen graphics




file system devices




Ethernet device



mouse(3) [aka cursor]

kernel mouse interface




running processes




real–time clock and non–volatile RAM




server registry



uart(3) [aka eia]

serial communication control




USB Host Controller Interface




VGA controller device




hardware watchdog timer




Personal Computer Memory Card Interface Association (PCMCIA) device




two–way interprocess communication




capabilities for setting the user id of processes




ATA–over–Ethernet (AoE) interface




external keyboard input




keyboard map




two–wire serial interface (TWSI) and inter–integrated circuit (I⁲C) interface

For now it is enough you are aware these things exist in Plan 9. If you must understand more before continuing, read the Name space subsection of the Description section of the intro(2) man page, all of namespace(4), namespace(6), and The Use of Name Spaces in Plan 9 paper, as well as other related links you encounter.

Step 4-1-1: Our First ls Command

Now run the following very familiar commands: {pwd ; ls -l} to see glenda's home directory:

It is clearly easier to point out the differences than the similarities to what you're used to seeing.

The file types (the first character of the first field) are limited to the usual d and - meanings plus the new a that means append-only file; i.e., not truncatable. There are no symlinks, no block or char device mknod entities, no FIFOs, etc.; just directories and two flavors of file.

There is a new second character inserted into the modes field, which signifies the entity may be open by no more than one process at a time (l) or not (-).

There are no other permission possibilities than w, r, x, and -. You won't encounter any sticky, setUID, setGID, or immutable bits.

There is no hard links count, because there are no hard links in Plan 9.

In Plan 9, directory entries that begin with a period (dot) are not special in any way. However, you cannot create a file or directory named "." or ".." because these are reseved names that have the indirect referencing significance to which you are accustomed. Especially notice "." and ".." do not exist in any directory—a Plan 9 directory can be truly empty.

Instead of the major and minor device node columns for only char and block files that you're used to, all entries display the underlying device character and instance identifier (the former can be looked up in that table I provided a few full-screen scrolls back).

If you'd like, take a quick look at the ls(1) man page (it really is just one page).

Step 4-1-2: The / Directory

Glenda's home directory is kind of boring, so let's look at something that ought to be more engaging: {ls -l /}:

That /dev directory is downright startling, is it not? We are clearly not in Kansas anymore, Toto. There are two /env directories ("That's interesting…" notes Captain Sparrow) and two /fd and /mnt directories, too. Here's the second part plus the results of an {ls -ld /} command since the first command did not display the / directory itself (no dot files or an ls -a option to show them, remember?):

Glenda owns the root directory (both / and /root) but cannot write to them?

We can explain the duplicate directories with the help of some analysis of what the {ns} command tells us about the name space we are examining:

and showing what namespace(4) tells us about /fd and /mnt: "All empty unwritable directories, place holders for mounted services and directories." So even though ls(1) says nothing about it, it is pretty clear the duplicate directories indicate the presence of union directory instances. Presumedly they are displayed in their search order, and the ns output reveals which is to receive or reject new entities via the -c flag.

Step 4-1-3: The /root Directory

To answer the / and /root directories we need to look at the /root directory using a {ls -l /root} command:


The /root directory appears to be the actual system root directory in the UNIX sense of the concept. In Plan 9 its condition is massaged into a custom view for each process group, each having its own / definition. We see this with the /tmp directory that is not a union directory but is replaced (just as a UNIX directory used as a mount point becomes hidden so it and its contents are inaccessible). The {ns | grep '\/tmp'} shows that Glenda's tmp (in /usr/glenda) is where /tmp files are served from.

We're almost ready for that {9fat:} command.

Step 4-1-4: The /n Directory

Let's look at directory namespace(4) says is "A directory containing mount points for file trees imported from remote systems" via an {ls -l /n} command (I guess the n stands for network):

These do appear to be mount points for non-root partitions Bill Gates would love as well as some he probably would not.

Step 4-1-5: Run the 9fat: Command

So we need to run the {9fat:} command according to the Installation Instructions, eh? There isn't a man page for that. The rc shell's whatis builtin command says it's external to the rc(1) shell (and you really want to make some time to study that man document—rc is neither sh nor bash). So we invoke an {ls -l /bin/9fat:} command, see it's small, and just issue a {cat /bin/9fat:} command to print the whole thing:

I do hope you are now ready for an exercise: figure out what this script will do before you tell it to (consider this an opportunity to study that rc manual as well as Rc — The Plan 9 Shell, its associated paper). Then run the command and check its outcome.

My verification is to look at /n/9fat before and after running {9fat:} to demonstrate the mount was successful:

Subsection 4-2: Preliminary Reconfiguration

Step 4-2-1: Modify plan9.ini vgasize

We should be able to disregard monitor redefinition because we are using a virtual VGA monitor for which the vesa specification should just work.

If you're up for using sam(1) or acme(1) you won't need to do exactly what I document here using ed(1) . After launching an {ed /n/9fat/plan9.ini} command and seeing it loaded the expected number of bytes into the ed buffer, use a {,} subcommand to display all lines in the buffer (well, technically that's a target that defaults to the first and last lines (and everything in between) with a null subcommand that defaults to p for print):


We see the line to change is next to last, so we could target $-1, but instead I'll demonstrate targeting using a backwards match:


following up with {.} to show the change, {w} to update the file, and {q} to quit ed.

Step 4-2-2: Modify /adm/timezone/local

The stand-alone terminal ISO does not permit logging on as the adm user, so we cannot do as the Installation Instructions instruct. However, glenda can authorize glenda to access the namespace as though the user is adm (I told you the security of this system out of the box isn't ready for the Internet).

The con(1) command and the fossilcons(8) server man pages explain the magic we are about to invoke to empower glenda to act as adm. First we launch a session with the fossil console by launching a {con -l /srv/fscons} command which encourages us to continue with a prompt (sometimes fscons is the party in need of encouragement via Enter). Then we instruct it to make the change via a {uname adm +glenda} subcommand, which fscons indicates was not a problem by giving us another prompt. All that remains now it is say goodbye by escaping the session dialog via Ctrl-\, {q}, and Enter.

Now we need to determine what our time zone options are, so we'll just see what we have to work with via a {cd /adm/timezone && lc} compound command:


Note that local and README are not good choices here.

For me, US_Eastern is the one, but you choose whatever works in your situation (refer to the Installation Instructions if you need to roll your own). We make our choice the local default by running a {cp US_Eastern local} command or its ilk, then (if you're like me), we take away glenda's new superpower on general principle:

Step 4-2-3: Reboot Prior to Major Reconfiguration

Our virtual machine environment seems to require us to to cycle the vm's virtual power for Plan 9 to be able to use see some types of reconfiguration when rebooting, so we modify the procedure documented in Installation Instructions under the Shut Down heading. We command {fshalt} and let the system quiesce to the following point:

Now we ensure we have the XFCE pointer, then left-click on the Virtual Machine item in the menu bar, mouse-over the Shut Down item that drops down, and left-click on the Force Off item that pops up, then left-click on the Yes icon in the lower left of the confirmation dialog that pops up. This destroys the instance of the vm (it's still a defined vm) and displays the status window below. Now we left-click of the Virtual Machine item in the menu bar once more, but this time we left-click on the Run item that drops down:

This creates a new instance of the vm and starts it booting. Respond to the root prompt appropriately (the default should still be correct), and specify {glenda} as the user again.

We should see Plan 9 has customed the windows to make better use of the available pixels if we expanded vgasize—here's 800x600 (but the cookbook will continue along in 640x480 to simplify screenshot handling):

Now we leave the {Installation Instructions} document and begin working through Configuring a Standalone CPU Server paper as promised at the beginning of this section.

Subsection 4-3: Install Scripts From USB

Step 4-3-1: Prepare the USB-stick

There are four text files you need to copy into a directory of a vfat USB-stick using the p9host system:

I expect you do not need to be told how to prepare this media, but if that is not the case in your case, Google is your friend.

Step 4-3-2: Enable Virtual USB Access

In order to attach a USB device to a virtual machine, the libvirt-qemu user needs to enjoy the same access to the device root does. The precise approach approach to use is up to you. What I did was add a rule to the p9host's udev configuration to associate any USB device with the libvirt-qemu group:

Once that went into effect, the p9host's sd* USB devices were affected—sdh is the vfat drive I copied the files to:

Step 4-3-3: Attach USB-stick to Plan 9 Server

Back in the glenda session we look at the /dev directory using an {lc /dev} command and see only the hard drive sdC0 and ISO image sdD0 storage devices—the virtual machine does not know the USB-stick exists.

This procedure will transfer use of the USB-stick from the Sid host to the Plan 9 vm. We begin by left-clicking on the View item on the menu bar, then left clicking on the Details radio button that drops down to switch the screen from the console to the vm control panel, like so:

In the Overview panel, we left-click on the Add Hardware icon in the lower left corner:

Now we left-click on the USB Host Device item in the column on the left:

We find the item describing the USB-stick and left-click on it to highlight it:

then left-click on the Finish icon in the lower right corner:

which returns us to the Overview panel. Notice there is a new device in the form USB hhhh:hhhh listed in the left column. Lastly we left-click on the View item in the menu bar again but then left-click on the Console radio button that drops down to return to the virtual machine's console:

Do you see the change? That reverse-video pop-up message that notifies us Plan 9 has noticed the attachment? If you don't see it on your display, you'll need to troubleshoot the failure to process the attachment.

That pop-up will dissipate shortly. If you don't want it (and other messages like it) to pop up so, contributed this suggestion:

Thanks for the tip!

When we know Plan 9 is now aware of the USB-stick, we issue the {lc /dev} command again to see what its file name is:

So the new kid on the block is named sdU0.0. Let's take another look at what p9host sees:

Your block devices for your USB-stick should have gone to the same place my sdh and sdh1 nodes have gone.

Step 4-3-4: Copy Files (with env and keyboard Tutorials)

We need to make that directory of text files available to Plan 9 by mounting sdU0.0 as a vfat filesys. We do that, according to the usb(4) man page, by running a {usbfat: sdU0.0} command, which tells us where to find the USB-stick's root directory. Now we can compose the commands to copy the text files where they need to be installed within /usr/glenda and also set the executable flags for bin/rc/env while we're at it, then dismiss the the USB-stick via a {usbeject sdU0.0} command (detaching the stick from the vm is left as an exercise for you (hint: look for the Remove icon in the panel for the USB-stick device).

Now let's check our work with a little tutorial about Plan 9 environment variables. If we issue the command {env -h} and all is well, the env script we installed should display the script's help information. If so, then you can understand what a {env -e prompt} command should do: display the value of the prompt environment variable using the echo command. What that displays is the fact that prompt is a list of two elements containing some white space, a blank for (1) and a tab for (2), but you can't tell that from that display. The {x} display can by invoking xd(1) to show the value(s) in hexadecimal, as we see in the {env -x ifs} results, showing the field separator variable consists of the blank (20), tab (09), and newline (0a) values, terminated by a null byte (which indicates ifs is a list variable with only one element:

Now let's get everything env will give us about the fn#ι variable. Right off the bat you have two questions: (1) What the heck is that non-ASCII character at the end? and (2) How the heck do I key it in?

I'm glad you asked. (1) Many people will recognize it as the lowercase Greek character iota. However, few, if any, will notice it is actually the APL monadic operator also called iota, that has its own UTF-8 encoding apart from the usual Greek character—the two are as different to Plan 9 as 1 and l are on any computer. Wikipedia has a nice page that explains all the different iotas. It calls this iota "APL FUNCTIONAL SYMBOL IOTA" under the "Technical iota" bullet, which is Unicode U+2373, UTF-8 E2 8D B3, and HTML &#x2373; i.e., .

(2) The keyboard(6) man page reveals everything you need to know about keying non-ASCII characters into Plan 9. Note I will show compose sequences by separating component keypresses with a single dot and all in italics. Keep in mind that it is often faster to snarf-and-paste an obscure character than to identify its encoding and key in its compose sequence. For the APL iota there is no shorthand (do not use the Compose.*.d shorthand—it looks close, ι, but it's no cigar). The correct keying is Compose.X., but the glyph will not look anything like it should (?).

Let's look at this more closely. We key in a here document that gets written to a file in which we key the ASCII shorthand characters followed by a left angle bracket, then the actual compose sequence, a right angle bracket, and Enter. The first line is for the Greek lowercase iota and the second for the APL iota. After closing the here document, we cat and xd the file. The xd clearly shows the correct UTF-8 encoding of the APL iota but the screen is rendering an unknown glyph for it.

Nonetheless, we try the correct incantation by keying {env -lecx 'fn#Compose.X.'} and Enter.

Because, in this context, a naked sharp would be interpreted as a comment-starter by rc, the variable name must be escaped by enclosing it within apostrophes (there is no other escape mechanism rc supports). The echo display only says "null list" because, as the {echo $#fn#ι} command following shows, the list contains zero elements, and echo doesn't deal with empty lists well (notice the sharps don't need to be escaped in this context). The ls display shows the variable is indeed a file backed by the #e driver and certainly does not hold zero bytes. The cat display shows the actual function that was defined in the env command and persists after it ends (which is why the following echo command has something to work with). To unset a Plan 9 variable, use the rm command on its /env directory entry.

By the way, the world-writable permissions you may have noticed in the ls output are not the security hole you might immediately think. also contributed this explanation:

And another tip of the hat!

Subsection 4-4: Run term2cpu to Reconfigure

We launch the heavy lifting reconfiguration script by issuing a {rc term2cpu} command.

Step 4-4-1: Set the Reconfiguration Parameters

We are immediately asked the first of six questions. Answer this one with the host name you have defined in your DNS zone for this cpu/auth server. Answer the second question with the domain name in which you have defined this cpu/auth server. Answer the third question with the name you wish to use in the ipnet entry in the /lib/ndb/local network database for the IPv4 subnet to which you have connected this cpu/auth server (this is Plan 9 internal use, not a concern of DNS). Answer the fourth question with the IPv4 address of the primary DNS server this cpu/auth server is to query (in dot-quad format; i.e, a.b.c.d where a, b, c, and d are unsigned decimal numbers between 0 and 255 inclusive). Answer the fifth question with the IPv4 address of this cpu/auth server (in dot-quad format). Answer the sixth question with either the CIDR (number of network bits in the previous IPv4 address, which must be between 1 and 31 inclusive) or the netmask associated with the previous IPv4 address (in dot-quad format).

Notice the message(s) before the blank line and deal with any concerns they mention in the new phase of the parameter definition process: final answers.

As the screen says, you now have the opportunity to change what you already provided (perhaps in error) as well as everything else needed that has been surmised by the script. You can keep changing a variable if necessary up to the time you respond with a null string. After that point you have to interrupt the script via Del and rerun the {rc term2cpu} command to get it right.

Notice the tz variable is the first line of the actual entry you put in the /adm/timezone/local file, not the name of the file. It contains pairs of timezone abbreviations and their adjustment seconds to apply to UTC to attain the local time, with the first pair defining standard time and the second, if present, defining daylight saving time. If you want to change it, you'll have to interrupt the script and change the timezone again as described in Step 4-2-2: Modify /adm/timezone/local before rerunning term2cpu. Changing the value of tz accomplishes nothing.

After dealing with the vgasize variable, the final answers are displayed.

This is your last chance to prevent reconfiguration using this set of parameters, so look closely and be sure you are satisfied with the set. If you have not read past this point before you press the Enter key, don't press it yet! Read on first and be certain what the unmodified script will do is what is right for your project, and if it is not, press the Del key and edit the script so it will do what is needed instead.

Step 4-4-2: Reconfigure the Server

Now it begins. These screen shots are provided to help you determine if you need to modify the term2cpu script before you let it reconfigure the stand-alone terminal. There is only one place where further input is requested, to say twice what password to set for the NVRAM machine key (which are not echoed to the screen). These screenshots can also help identify any deviations from what the cookbook expects to happen with what you experience. This commentary will resume when the script has ended.

Step 4-4-3: Shut Down the Reconfigured Terminal

It is finished. Before running that {fshalt} you have this opportunity to tweak things first. When ready to proceed, run that command and let the system quiesce.

Subsection 4-5: Finish the Reconfiguration

Again we virtually cycle the power to the virtual machine using the Force Off / Run method of shutting down.

Step 4-5-1: Boot Up the Server

During the boot up, the new menu will ask which kernel, and we select the one we just built via the 2 option. Notice you have ten seconds to respond to this prompt before 2 is automatically set.

Respond with {local!#S/sdC0/fossil}—override the default if necessary:

Now we provide the NMRAM reconfiguration information. Notice the secstore password is limited to 12 characters.

Step 4-5-2: Set Up Access and New Account

The grey rio background should appear.

Right-click select New and draw a new full-screen window:

Run an {auth/changeuser bootes} command to incorporate bootes into the auth server. Make certain this password is identical to what you specified for bootes during the NVRAM reinitialization. Except for {n} for the Inferno/POP prompt, we only Enter for all the other prompts.

Now run an {auth/changeuser} command for the administrative account you want, changing my p9adm user name to whatever you wish. This time reply {y} to the Inferno/POP prompt and use the same password or another according to your project's requirements. Use your real name and email for this account so folks can find you if they need to.

Lastly, modify the fossil access database to know your admin user and to have sys and adm rights.

Subsection 4-6: Connect Via Drawterm

Here comes the acid test: accessing the server remotely. In the p9host user account under Sid, run the {drawterm -a p9host.home -c p9host.home -s p9host.home -u p9adm &} command substituting your particulars for mine:

This window should pop up with the password prompt. If so, reply with the account's password, and you should get a prompt.

Notice this is not a rio window—there is no pointer or mouse interaction. Also the home directory does not exist. We fix all this by running the {/sys/lib/newuser} command when a new user logs in the first time. That should result in a rio environment:

In this X window, the pointers are slightly different from the virtual machine's console. Left-click to get to the arrow pointer:

Then right-click brings up the rio menu window:

so you can draw a new window:

An {ls -l} command confirms you're in your new home directory on a remote Plan 9 cpu/auth server. also contributed this suggestion:

And a third tip of the hat!

That's it for this recipe and book. Have a great time exploring this platform! Subscribe to the 9fans mailing list and introduce yourself while letting everyone know you were able to join the club using this cookbook.

Prev: Install Stand-Alone Terminal

Up: Top

Next: Top