Using Systemd To Enable Wake-On-Lan

What is this about?

I made a previous post (Enabling Wake-On-LAN (In Ubuntu 20.10)) about enabling Wake-On-LAN on my server, but it didn't seem to work after a re-boot. I looked in the logs using journalctl and noticed that the wol.service that I set up didn't get enabled after the re-boot. I thought about it and I remembered that when I was working with bluetooth and set up a service for it I had to make it wait for the bluetooth service to start first or it wouldn't work and realized that there was nothing in the wol.service file to tell it to wait so I went poking around and found what I hope to be a fix.

So, how do you do it?

We need to update the /etc/systemd/system/wol.service that I created in that earlier post. Previously it looked like this:

[Unit]
Description=Enable Wake On Lan

[Service]
Type=oneshot
ExecStart = /sbin/ethtool --change enp4s0 wol g

[Install]
WantedBy=basic.target

We need to update the [Unit] section by adding

Requires=network.target
After=network.target

To let systemd know that we don't want this to run until after the network service has run. I also changed the WantedBy to multi-user.target. So the updated file looks like this:

[Unit]
Description=Enable Wake On Lan
Requires=network.target
After=network.target

[Service]
Type=oneshot
ExecStart = /sbin/ethtool --change enp4s0 wol g

[Install]
WantedBy=multi-user.target

Now reload the daemon and re-enable it.

sudo systemctl daemon-reload
sudo systemctl enable wol.service

To make sure I didn't introduce any errors I ran it once.

sudo systemctl start wol

Then I rebooted it.

sudo reboot now

This time in journalctl I saw this:

Dec 07 00:02:17 erebus systemd[1]: Finished Enable Wake On Lan.
Dec 07 00:02:17 erebus systemd[1]: wol.service: Succeeded.

And running ethtool showed that it was enabled.

Wake-on: g

So, That Fixed It?

Well, insofar as it's working now, yes. But since I change more than one thing I don't really know exactly what did it (was it one of the three lines I changed or all of them?). Anyway, since it works I'll let it lie for now.

Where did you find out about this?

  • The Arch wiki has a page on Wake-On-LAN that tells you many different ways to set it up – I copied the systemd file almost exactly. It has quite a bit of useful information (too much maybe), but not all the commands will work out of the box on Ubuntu (because it's Arch).

Mount An Encrypted Drive Using cryptsetup

What Is This About?

This is how to mount a LUKS encrypted drive with cryptsetup in Ubuntu 20.10 (Groovy Gorilla). My use-case is I have a headless server that has an external USB drive that's encrypted with LUKS that I need to mount from the command line. I like the flexibility it gives a little more than the what the Gnome GUI does so I'll probably use it on my desktop as well. I previously documented how to do it with udiskctl in this post but I like cryptsetup a little better. This assumes that you know the partition/device (in my case it's /dev/sdb1). That previous post shows how I found it so I'll just do the decrypting and mounting here.

So, How Do You Do It?

The syntax to decrypt the device is:

sudo cryptsetup open <device> <name>

As I mentioned above, in this case my device is /dev/sdb1. The name can be an arbitrary one (although there are limitiations on special characters so let's say it can be any alpha-numeric name) and I'll use wddata as the name. Then my command to decrypt the drive is:

sudo cryptsetup open /dev/sdb1 wddata

This will prompt you for your password and then the encryption passphrase. Once the command succeeds, it adds a link in /dev/mapper using the name you passed in so you can mount the device (assuming the directory you want to mount it in exists) like this:

sudo mount /dev/mapper/wddata /media/data

Where wddata is the name I told cryptsetup to use and /media/data is a directory that I had previously created.

Source

Besides the man-page for cryptsetup on my server, I was pointed to use cryptsetup from this StackOverflow post:

The accepted answer uses udiskctl but further down is an answer by George Schölly using cryptsetup. The syntax in the answer is the older cryptsetup syntax (which should still work) so it doesn't look exactly like what I used.

Enabling Wake-On-LAN (In Ubuntu 20.10)

Note: The systemd configuration here isn't quite right, but since this post is kind of long and convoluted I made a standalone update about the systemd configuration file in this post.

Beginning

These are my notes on getting Wake-On-LAN working in Ubuntu 20.10. I have a server that I use to run most of the computation on when I use emacs/jupyter but I have it in a corner upstairs and although it's only a little walk, I find that the fact that I have to stop what I'm doing and go upstairs to push that little button on the front makes me lazy and so it ends up running more than it has to so I thought I'd enable Wake-On-LAN so I can suspend it and wake it up whenever I need to. I'm only going to use suspend (APM S3). When I tried to use hibernate (S4) it ended up shutting down my machine (S5). Interestingly, my BIOS menu has an option to enable waking up from shutdown, but since my disk is encrypted, and I didn't set up a separate SSH server, I have to go enter the passphrase to unlock the disk before the operating system can boot up, so it kind of defeats its own purpose.

Middle

Ethtool

The command I used to set up Wake-On-LAN on the remote machine is called ethtool. It's in the Ubuntu repositories but wasn't installed on my machine so I had to add it.

sudo apt install ethtool

Checking the Interface

From what I've read, not all ethernet interfaces support Wake-On-LAN (although I've never seen one that doesn't) so a quick check might be useful. First, find the name of your ethernet interface.

ip a

My machine shows four interfaces so I'll just show the output for the interface I'm interested in rather than the whole output for the command.

2: enp4s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 38:d5:47:79:ab:0b brd ff:ff:ff:ff:ff:ff
    inet 192.168.86.97/24 brd 192.168.86.255 scope global dynamic noprefixroute enp4s0
       valid_lft 84752sec preferred_lft 84752sec
    inet6 fe80::685d:374d:a577:f787/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

Ethtool uses the name of the interface, in this case it's enp4s0, so we'll need to note that. Additionally, the machine that I used to wake up the machine needs the MAC address (38:d5:47:79:ab:0b) so it'd be useful to write that down someplace. I'm waking it up from the LAN so the IP address isn't so important, and to be able to SSH into it I need to know it anyway, so it's really those two pieces of information that I need. Now to check if it supports Wake-On-LAN.

sudo ethtool enp4s0

Ethtool will give you some information if you don't run it as root but for Wake-On-LAN you need to run it as root. The important lines in the output is near the bottom and it looks something like this if it supports Wake-On-LAN.

Supports Wake-on: pumbg
Wake-on: d

The man-page for ethtool tell you what that cryptic pumbg means - the letters are different options that this interface supports for Wake-On-LAN. In this case they are:

Option Description
p Wake on PHY activity
u Wake on unicast messages
m Wake on multicast messages
b Wake on broadcast messages
g Wake on MagicPacket messages

There's an additional option which is what the interface was set on – d – as you can see in the last line of the output. This means Disable (wake on nothing). This option clears all previous options. I don't have many devices on my network, so I don't know that there's a lot of broadcasts, multicasts, etc. that would be waking it up all the time, but since one feature of Wake-On-LAN is that it only wakes the machine when it gets the "Magic Packet", only the g and d options matter. Now that I knew it was supported, it was time to try it out.

Turn It On Temporarily

The ethtool will turn on Wake-On-LAN, but (supposedly) everytime you reboot the machine it will reset to disabled. I haven't really tested this out, but I'll document how to make it permanent later, anyway.

sudo ethtool --change enp4s0 wol g

So, as you might guess, we changed the Wake-On-LAN setting to listen for MagicPacket messages. You can check using the ethtool again.

sudo ethtool enp4s0

The Wake-on line should have changed to:

Wake-on: g

Now to suspend the machine so we can test it out.

sudo systemctl suspend

Test It Out

Now, on my local machine I needed to install wakeonlan. There's a surprising number of programs to send the Magic Packet, but this just happened to be the one I used.

sudo apt install wakeonlan

The default way to use wakeonlan is apparently to just pass it the MAC address of the computer to wake up, and it will send the Magic Packet out as a broadcast, so that's what I did.

wakeonlan 38:d5:47:79:ab:0b

And then I pinged the machine and I waited. And I waited. And I waited… Eventually I went upstairs and saw that it was still sleeping so I pushed the power button to wake it up and went back downstairs.

Take Two

Something wasn't right so I SSHd into the server and started up tcpdump to see if the packets were going through.

sudo tcpdump -i enp4s0 '(udp and port 7) or (udp and port 9)'

Which gave me this output:

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp4s0, link-type EN10MB (Ethernet), capture size 262144 bytes

And then I sent the Magic Packet again.

wakeonlan 38:d5:47:79:ab:0b

…And nothing happened. For some reason the packets weren't getting picked up by the machine. Luckily, wakeonlan lets you pass in an IP address as an option. The man page recommends using a broadcast address, but I have the IP addresses of my machines on the LAN reserved on my router/access-point so I just passed in the full address (I did try the LAN broadcast and it worked too).

wakeonlan -i erebus 38:d5:47:79:ab:0b

I have my machine's IP address aliased in my /etc/hosts file so erebus is just an alias for the machine's IP address. The subnet broadcast version looked like this.

wakeonlan -i 192.168.86.255 38:d5:47:79:ab:0b

The output from tcpdump for the first packet looked like this.

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp4s0, link-type EN10MB (Ethernet), capture size 262144 bytes
20:47:11.689587 IP 192.168.86.141.34805 > erebus.discard: UDP, length 102

So, something was different. I suspended the machine again and sent the Magic Packet and this time it worked. Go figure.

Making It Permanent

Set It Up

The reasons that I said earlier that the Wake-On-LAN setting "supposedly" is temporary is that:

  1. I haven't really re-booted that machine to test it out (I have rebooted, but I haven't disable the systemd service that I'm documenting here).
  2. The machine that I'm typing this on had Wake-On-LAN enabled and it doesn't have a systemd service enabled.

But, really, I don't remember even enabling Wake-On-LAN on this machine so maybe it just was the default and I didn't realise it… another thing I should look into one of these days. Anyway, to make a service that always enables Wake-On-LAN the first step is to find the path to ethertool.

which ethtool

In my case the path was /sbin/ethtool, so once you know this you can create a file at /etc/systemd/system/wol.service (I think you can use another systemd sub-folder, and you can name the file anything you want, within reason, but this one seems to work well enough). In this file you put settings that look something like this:

[Unit]
Description=Enable Wake On Lan

[Service]
Type=oneshot
ExecStart = /sbin/ethtool --change enp4s0 wol g

[Install]
WantedBy=basic.target

The only thing specific to my machine is enp4s0, the name of the ethernet interface, although it's possible that the path to the ethtool executable might be different too… but it should be the same on Ubuntu 20.10, anyway.

Enable The Service

To enable it you can do this:

sudo systemctl daemon-reload
sudo systemctl enable wol.service

Where wol.service is the name of the file you created with the settings. You can check its status if you want.

systemctl status wol

And that's that.

End

So, that's how I got one machine working with Wake-On-LAN. Hopefully I won't have to look so hard the next time. Here's the pages that I stole this from.

  • TechRepublic on using ethtool and setting up a systemd service for this (don't use the systemd file here, though).
  • Stack Overflow on how to suspend and hibernate from the command-line
  • Stack Overflow on what the difference is between suspend and hibernate
  • Stack Overflow on using tcpdump to look for the Magic Packets on the remote machine
  • Stack Overflow on editing remote files as root with emacs (not documented here, but maybe later)
  • Stack Overflow on editing a local file as root with emacs (not used here, but I can never remember the syntax)

Memoization and the Fibonacci Sequence

What It Is

This is a note on using memoization with recursion - specifically with the generation of a Fibonacci Number. The fibonacci numbers form a sequence where

\[ F_0 = 0, F_1 = 1 \]

and then for the rest of the numbers greater than 1

\[ F_n = F_{n-1} + F_{n-2} \]

So, starting from 0 you get 0, 1, 1, 2, 3, 5, 8, etc.

Recurse

Calculating the sequence is often done with recursion because you can pretty much take the definition and convert it to a function with little translation.

def fibonacci(n: int) -> int:
    """Calculates the n-indexed fibonacci number

    Args:
     n: the index of the number in the (zero-based) sequence to get

    Returns:
     fibonacci number at index n
    """
    assert n >= 0
    if n < 2:
        return n

    return fibonacci(n - 1) + fibonacci(n - 2)
expected = 55
actual = fibonacci(10)
print(actual)
assert expected == actual, actual
55

Remember that n is the index for number, not the count (the second number in the sequence has index 1).

The problem here is that recursion like this has memory limits and takes a long time.

from graeae import Timer
TIMER = Timer()
expected = 102334155
n = 40
with TIMER:
    actual = fibonacci(n)
    print(n)
    assert actual == expected, actual
2020-11-09 21:11:05,852 graeae.timers.timer start: Started: 2020-11-09 21:11:05.852071
2020-11-09 21:11:50,249 graeae.timers.timer end: Ended: 2020-11-09 21:11:50.249808
2020-11-09 21:11:50,251 graeae.timers.timer end: Elapsed: 0:00:44.397737
40

Less than a minute might not seem like a big deal, but even pushing it up to 45 makes the wait too long (I gave up and killed the process so I don't know how long it ran). So what's the solution? Let's try memoization.

Memoize

So, what's memoization? Is it what Elmer Fudd does when he memorizes? No, memoization comes from the latin word memorandum which means "to be remembered". What we're going to do is create a cache dictionary that will match arguments to our function call to their outputs. Then if a function call comes in that uses arguments that were used before, we can just grab it from the cache instead of re-doing the calculations.

We're going to use a python decorator from functools named wraps that allows you to build a decorator that looks like the original function. It isn't necessary for the decorator to work, but it makes it look more like the original functior passed to the decorator so it's a good practice to use it.

from functools import wraps
def memoize(function):
    """Adds caching

    Args:
     function: the callable to memoize

    Returns:
     callable with caching and the original function
    """
    cache = {}
    @wraps(function)
    def wrapped(*args):
        if args not in cache:
            cache[args] = function(*args)
        return cache[args]
    return wrapped

Although it works as a decorator, since we already defined the fibonacci function we can just pass it to memoize to add the cache.

fibonacci = memoize(fibonacci)

Now let's see what happens.

with TIMER:
    actual = fibonacci(100)
print(actual)
2020-11-10 15:45:08,117 graeae.timers.timer start: Started: 2020-11-10 15:45:08.117332
2020-11-10 15:45:08,118 graeae.timers.timer end: Ended: 2020-11-10 15:45:08.118691
2020-11-10 15:45:08,120 graeae.timers.timer end: Elapsed: 0:00:00.001359
354224848179261915075

Note: I originally tried renaming the memoized function, but since the recursive calls go to the original function name, this doesn't produce and improved function. You have to use the same name as you did when you defined the function.

So memoization really helps, even more than you might expect. The reason why is that the first recursion term (fibonacci(n - 1)) gets evaluated first, so each recursive call goes backwards by one until it hits the base-case where n < 2 and then all the rest of the calls are evaluated, but after one run through the indexes you've already hit all the cases you need for these other calls so rather than making more recursive calls, everything gets pulled from the cache.

Once Again With Python

As is often the case, when you implement something useful in python you'll find that it's already been implemented, in this case as part of the python standard library.

from functools import lru_cache

@lru_cache(maxsize=None)
def fib_o_nacci(n: int) -> int:
    """Calculates the n-indexed fibonacci number

    Args:
     n: the index of the number in the (zero-based) sequence to get

    Returns:
     the nth fibonacci number
    """
    assert n >= 0
    if n < 2:
        return n

    return fib_o_nacci(n - 1) + fib_o_nacci(n - 2)

Note: in python 3.9 there is a cache decorator that is the same thing as the lru_cache with maxsize=None but I'm running this on python 3.8 right now so I can't use it.

with TIMER:
    print(fib_o_nacci(500))
2020-11-09 21:53:19,405 graeae.timers.timer start: Started: 2020-11-09 21:53:19.405955
2020-11-09 21:53:19,407 graeae.timers.timer end: Ended: 2020-11-09 21:53:19.407891
2020-11-09 21:53:19,409 graeae.timers.timer end: Elapsed: 0:00:00.001936
139423224561697880139724382870407283950070256587697307264108962948325571622863290691557658876222521294125

Okay, so, I think it works, although I'm not checking the values, the speed seems to be an improvement.

Brute Force Longest Non-Decreasing Sub-Sequence

The Problem

The problem at hand is that we're given a sequence and we want to find the longest combination we can find in it that is non-decreasing (each element after the first is greater than or equal to the previous element).

Brute Force

This will be a brute-force solution using nested for-loops. This will use the itertools.combinations function to generate the candidate solutions.

from itertools import combinations
def brute_force(sequence: iter) -> tuple:
    """Finds the longest non-decreasing sub-sequence

    Args:
     sequence: the source collection of items

    Returns:
      the longest non-decreasing sub-sequence in the sequence, number of for-loops
    """
    count = 0
    for length in range(len(sequence), 0, -1):
        count += 1
        for sub_sequence in combinations(sequence, length):
            count += 1
            if list(sub_sequence) == sorted(sub_sequence):
                return sub_sequence, count
    return

Here's what it's doing.

  1. The outer loop counts down from the length of the sequence to zero.
  2. The inner loop generates combinations of the current length in the outer loop
  3. If the sub_sequence is in sorted (non-decreasing) order then it is as long or longer than any other non-decreasing sub-sequence so choose it as the longest sub-sequence.

Example One

source = (3, 1, 0, 2, 4)
expected = (1, 2, 4)

actual, count = brute_force(source)
print(actual)
print(count)
assert actual == expected
(1, 2, 4)
18

Python Itertools Combinations

What?

This is a brief note on the python itertools.combinations function.

from itertools import combinations

To make sure it's behaving the way I think it will I'll also use some other built-ins.

from itertools import count
from math import factorial
from math import comb as n_choose_k

Okay, but what?

The combinations function takes two arguments:

  • iterable: the source that you want to make combinations from
  • r: the length of the sequences you want to make

It returns all the r-length subsequences in the iterable (combinations).

A Count Function

The number of combinations of length k for a sequence of length n is the Binomial Coefficient.

\begin{align} \binom{n}{k} &= \frac{n(n-1) \cdots (n - k + 1)}{k(k-1) \cdots 1}\\ &= \frac{n!}{k!(n-k)!} \end{align}

I originally implemented a function for this but it turns out it got added to python as of 3.8 (number-theoretic functions). Here's my translation for reference. The calculation is actually really fast so you don't gain a lot from these toy problems using the standard library, but since it's there, why not?

def n_choose_k_one(n: int, k: int) -> int:
    """The Binomial Coefficient

    Args:
     n: the length of the source set
     k: the length of the combinations

    Returns:
     the number of combinations in the sequence
    """
    return factorial(n) / (factorial(k) * factorial(n - k))

Example One

This is given as an example in the documentation.

EXPECTEDS = "AB AC AD BC BD CD".split()
SOURCE = "ABCD"
k = 2
for combination, expected, total in zip(combinations(iterable=SOURCE, r=k), EXPECTEDS, count(start=1)):
    print(f" - {combination}")
    assert "".join(combination) == expected

n = len(SOURCE)
assert total == n_choose_k(n, k)
- ('A', 'B')
- ('A', 'C')
- ('A', 'D')
- ('B', 'C')
- ('B', 'D')
- ('C', 'D')

Note that it treated the string as a sequence and returns a tuple, not a sub-string. Also note that it doesn't swap orders, so "D" is never a first entry, for instance.

Example Two

This one is also from the documentation.

SOURCE = list(range(4))
n = len(SOURCE)
EXPECTEDS = [(0, 1, 2),
             (0, 1, 3),
             (0, 2, 3),
             (1, 2, 3)]
k = 3

for combination, expected, total in zip(combinations(SOURCE, k), EXPECTEDS, count(start=1)):
    print(combination)
    assert combination == expected

assert total == n_choose_k(n, k)
(0, 1, 2)
(0, 1, 3)
(0, 2, 3)
(1, 2, 3)

Changing the Firefox Reader Font

What is this about?

I've been trying for a while now to figure out how to change the Firefox Reader font. The default (serifed-font) is Georgia, which is nice enough, but I like to change things up now and again. I would sometimes just edit the CSS in the Developer's Tools, but I finally stumbled upon how to do it so I'll save it here for my future self.

So how do you do it?

The procedure is pretty simple, this is it in outline:

  1. Create the chrome folder for it if it doesn't exist.
  2. Enable the use of this folder.
  3. Find the CSS you want to change.
  4. Add a userContent.css file to the chrome folder and add the CSS you want to use to it.
  5. Restart Firefox

Creating the chrome Folder

The configuration files you create go into a folder named "chrome". Firefox used to create it for you with example files but doesn't anymore so if you haven't created one you'll have to do so now - but where? It goes into your "profile" folder, but since you can have multiple configurations the path isn't fixed. There's multiple ways listed to find it on the Firefox Profile folder page. The way I did it was to enter about:support in the address bar.

This brings up the Troupbleshooting Information page.

The bottom of the screenshot is what we want.

The path displayed is your profile folder (/home/athena/.mozilla/firefox/j3hyuwod.default-1603227893962). You can click on the Open Directory button to open it with the GUI or use the path to navigate to it from your text editor. Within that folder create a folder named chrome. Why chrome when this is firefox? Chrome is apparently not related to google's chrome, but, according to Eric S. Raymond (and Wikipedia, etc.) refers to the GUI, or at least some aspects of it. Anyway.

userContent.css

In the chrome folder create a file named userContent.css (here's a mozillaZine article with some information about it) and add your CSS. Looking in the Firefox Inspector with the Reader View open it looks like it just uses the defaults.

But I don't want to change it for every web page so I added it to the .moz-reader-content selector.

.moz-reader-content { 
    font-family: "TeX Gyre Pagella", Gentium;
}

Which I also found using the Firefox Inspector.

Enable It

So, you might think that we're done, right? But it turns out that even though Firefox gives you the ability to add custom CSS it doesn't load it by default. I couldn't find anything on the mozillaZine site about how to enable it (although it might be there, I just didn't find it), but there's a www.userchrome.org site that has a page on setting up userChrome.css (which also talks about creating the chrome folder). userChrome.css is how you can style the Firefox GUI itself (the chrome) so in this case anything specific to that file isn't relevant to what we're doing here, but the rest of the page is.

The Steps

First navigate to about:config.

If you hadn't previously unchecked the Warn me when I attempt to access these preferences button you'll be greeted with a warning page.

Once you click on Accept the Risk and Continue or just hit enter, depending on your setup, you'll get to the Advanced Preferences page.

In the search box at the top of the page type in userprof.

If it's already set to true then you're all set, otherwise click on the toggle button on the right side.

This should flip it from false to true.

Oh, and just one more thing…

Changing the preferences to use the stylesheet doesn't load the actual stylesheet. To actually get it all working restart Firefox and try out the reader on a page.

In my case it changed the Wikipedia page on Rhetorica ad Herennium from this.

To this.

Not that dramatic in this case, but I still haven't found my favorite font. Also only the content was changed, not the headers… I'll have to look into that. Anyway, if you look in the Inspector the new CSS should show up.

End

So, that's one way to change the font for the Firefox Reader. Not exciting, but finding out how to do it was hard enough that I thought I should note it for later.

Javascript in Org-Babel

Beginning

This is a test of running javascript in org-babel blocks. For some reason I couldn't find any documentation about setting it up so this is also a place for me to collect what to do.

Note: I actually did use this org-babel page for some of it, but it wasn't enough for me to really figure out what was going on.

Middle

Prerequites

Node

Since javascript is an interpreted language you need an interpreter to run code blocks. Org-babel assumes you're using node.js so you need to install it and make sure it's on the PATH. They do support debian-based systems (including Ubuntu, which I use) but they don't use the traditional PPA system. Instead they have different installs for the different versions - but they do have a Long-Term Support (LTS) version which I'm hoping updates so that's what I installed.

curl -sL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs

As of this writing it install version 12.19.0, versus the Ubuntu 20.04 version of 10.19.0.

init.el

Besides installing node you have to make sure that you add js to your org-babel set up.

(org-babel-do-load-languages
 'org-babel-load-languages
 '((js . t)))

If you already have other languages added this just goes in the same block.

(org-babel-do-load-languages
 'org-babel-load-languages
 '((plantuml . t)
   (shell . t)
   (emacs-lisp . t)
   (latex . t)
   (ditaa . t)
   (js . t)
   (python . t)
   (jupyter . t)
   ))

Try It

Now that it's set up you can execute javascript in org-babel code blocks, designating them as js blocks (e.g. #+begin_src js :results output :exports both).

Here's a simple output block.

console.log("test")
test
var x = 'apple'
console.log(x)
var y = 'banana'
console.log(x + y)
apple
applebanana

One thing to note is that this doesn't seem create sessions that persist across blocks. Even though I defined x and y in the previous block, this next block raises an error because it doesn't think I've defined y.

console.log(y)
console.log(x)

If you try and put a session argument in the org-babel header you'll get a message saying "Session evaluation with node.js not supported". According to the org-babel-js documentation you can put in special headers to run the code in an alternate REPL, but I tried it and the output ends up in a separate buffer rather than showing up in the org-document, which seems to kind of lessen the usefulness of it. Also the js-comint version dumps extra text into the REPL as well.

End

Okay, so this was a very basic hello world for javascript in emacs. The lack of sessions is kind of disappointing, but I don't know how useful this is going to be, anyway, since javascript is so tied to the browser, but there it is.