Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save stolsma/3301813 to your computer and use it in GitHub Desktop.
Save stolsma/3301813 to your computer and use it in GitHub Desktop.
Working Node.js on Raspberry Pi

These instructions work for the Raspberry Pi running Raspbian (hard float) and create a hardware optimized version of NodeJS for the Raspberry PI, (and include a working install and NPM!!!):

  1. Install Raspbian - http://www.raspberrypi.org/downloads

  2. Install the necessary dependecies:

sudo apt-get install git-core build-essential

(If you just installed git then you need to administer your git identity first, else adding the patches below will fail!!!)

  1. Check out Node.js source (0.8.8 or higher):
git clone https://github.com/joyent/node.git
cd node
git checkout v0.8.8 -b v0.8.8
  1. Push four patches for V8 and OpenSSL ARM support:

The following two patches are only needed when compiling Node version <0.8.10 (already committed for 0.8.10pre NOT yet for master)

Remark: The first patch is already commited upstream to V8 cutting-edge branch (node is on a lower version of V8). The 2nd patch is pushed to the V8 bug list.

curl https://github.com/joyent/node/commit/25c2940a08453ec206268f5e86cc520b06194d88.patch | git am
curl https://github.com/joyent/node/commit/1d52968d1dbd04053356d62dc0804bcc68deed8a.patch | git am

The following two patches are only needed when compiling Node version <0.8.9 (already committed for master and 0.8.9pre + higher)

curl https://github.com/joyent/node/commit/f8fd9aca8bd01fa7226e1abe75a5bcf903f287ab.patch | git am
curl https://github.com/joyent/node/commit/7142b260c6c299fc9697e6554620be47a6f622a9.patch | git am
  1. Configure correctly and 'make':
./configure
make
  1. (Optional!) If you want to do the tests then execute (make test can't be used because the standard timeout is too low, i.e. the Rasp PI is too slow... ;-)):
python tools/test.py -t 120 --mode=release simple message
  1. Install Node and NPM in your OS:
sudo make install

NOTE on 0.8.8 tests: some tests still have issues (8 tests fail, some are timeouts (as the PI isn't that fast ;-) and one is a known bug)!

After compiling node0.8.8 the following 7 (or 8 if IPV6 is not enabled) tests will fail. After checking 3 failing tests are related to the slow processing/slow filesystems, 1 is a known bug and 3 (repl problems) are under investigation.

The following is a known bug. Will be solved in one of the next releases of Node (see joyent/libuv#526).

=== release test-os ===
Path: simple/test-os
hostname = raspberrypi
uptime = 19738.871849515
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-os.js

The Rasp Pi is not that fast that it can complete the following test in time. ;-) If time is changed from 1000 to 10000 it will complete without error...

=== release test-child-process-fork-net2 ===
Path: simple/test-child-process-fork-net2
assert.js:102
  throw new assert.AssertionError({
        ^
AssertionError: timeElasped was not between 190 and 1000 ms
    at process.<anonymous> (/home/pi/node0.8.6/test/simple/test-child-process-fork-net2.js:128:12)
    at process.EventEmitter.emit (events.js:115:20)
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-child-process-fork-net2.js

Raspbian doesn't have IPv6 enabled by default and thats where this following test is counting on. if IPv6 is enabled (sudo modprobe ipv6) this test will complete without errors.

=== release test-net-pingpong ===
Path: simple/test-net-pingpong
server listening on /home/pi/node0.8.6/test/tmp/test.sock undefined
server listening on 20988 undefined
connection: 127.0.0.1
connection: undefined
events.js:66
        throw arguments[1]; // Unhandled 'error' event
                       ^
Error: listen EAFNOSUPPORT
    at errnoException (net.js:768:11)
    at Server._listen2 (net.js:891:19)
    at listen (net.js:935:10)
    at Server.listen (net.js:992:9)
    at dns.js:71:18
    at process.startup.processNextTick.process._tickCallback (node.js:244:9)
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-net-pingpong.js

The Rasp Pi is not that fast that it can complete the following test in time. ;-) If timeouts are changed from 10 to 100 and 20 to 200 it will complete without error...

=== release test-http-client-timeout-with-data ===
Path: simple/test-http-client-timeout-with-data
assert.js:102
  throw new assert.AssertionError({
        ^
AssertionError: 0 == 1
    at process.<anonymous> (/home/pi/node0.8.6/test/simple/test-http-client-timeout-with-data.js:30:10)
    at process.EventEmitter.emit (events.js:115:20)
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-http-client-timeout-with-data.js

The Rasp Pi is not that fast that it can complete the following test in time. ;-) If run seperately this test will complete without error!!!

=== release test-eio-race ===
Path: simple/test-eio-race
trying to kill event loop ...
could not kill event loop, retrying...
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-eio-race.js
--- TIMEOUT ---

All the following REPL test fail are under investigation (possibly due to timing issues generated by the slow processing of the Rasp PI).

=== release test-debugger-repl ===
Path: simple/test-debugger-repl
./node debug --port=13683 /home/pi/node0.8.6/test/fixtures/breakpoints.js
line> debug>< debugger listening on port 13683
line> debug>connecting... ok
line> debug>break in test/fixtures/breakpoints.js:1
line>  1 debugger;
line>  2 function a(x) {
line>  3   var i = 10;
line> debug>break in test/fixtures/breakpoints.js:11
Error: Timeout. Expected: /9/
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-debugger-repl.js
=== release test-force-repl ===
Path: simple/test-force-repl
timers.js:103
            if (!process.listeners('uncaughtException').length) throw e;
                                                                      ^
Error: timeout!
    at Object._onTimeout (/home/pi/node0.8.6/test/simple/test-force-repl.js:30:9)
    at Timer.list.ontimeout (timers.js:101:19)
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-force-repl.js
=== release test-debugger-repl-utf8 ===
Path: simple/test-debugger-repl-utf8
./node debug --port=13683 /home/pi/node0.8.6/test/fixtures/breakpoints_utf8.js
line> debug>< debugger listening on port 13683
line> debug>connecting... ok
dying badly
Error: Timeout. Expected: /break in .*:1/
Command: out/Release/node /home/pi/node0.8.6/test/simple/test-debugger-repl-utf8.js
@stolsma
Copy link
Author

stolsma commented Aug 16, 2012

If you followed the instructions in the 'Instructions node0.8.6' file then everything should work. I compiled v0.8.6 numerous times the last 4 days and it always worked.. ;-)

Because of the "Illegal Instruction" error, it looks like you didn't revert commit nodejs/node-v0.x-archive@90efdb3 or didn't apply the v0.8.2-release-raspberrypi patch. If you don't know how to revert the commit you can also replace the content of the 'configure' file with the following content:

#!/usr/bin/env python
import optparse
import os
import pprint
import re
import subprocess
import sys

CC = os.environ.get('CC', 'cc')

root_dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.join(root_dir, 'deps', 'v8', 'tools'))

# parse our options
parser = optparse.OptionParser()

parser.add_option("--debug",
    action="store_true",
    dest="debug",
    help="Also build debug build")

parser.add_option("--prefix",
    action="store",
    dest="prefix",
    help="Select the install prefix (defaults to /usr/local)")

parser.add_option("--without-npm",
    action="store_true",
    dest="without_npm",
    help="Don\'t install the bundled npm package manager")

parser.add_option("--without-waf",
    action="store_true",
    dest="without_waf",
    help="Don\'t install node-waf")

parser.add_option("--without-ssl",
    action="store_true",
    dest="without_ssl",
    help="Build without SSL")

parser.add_option("--without-snapshot",
    action="store_true",
    dest="without_snapshot",
    help="Build without snapshotting V8 libraries. You might want to set"
         " this for cross-compiling. [Default: False]")

parser.add_option("--shared-v8",
    action="store_true",
    dest="shared_v8",
    help="Link to a shared V8 DLL instead of static linking")

parser.add_option("--shared-v8-includes",
    action="store",
    dest="shared_v8_includes",
    help="Directory containing V8 header files")

parser.add_option("--shared-v8-libpath",
    action="store",
    dest="shared_v8_libpath",
    help="A directory to search for the shared V8 DLL")

parser.add_option("--shared-v8-libname",
    action="store",
    dest="shared_v8_libname",
    help="Alternative lib name to link to (default: 'v8')")

parser.add_option("--shared-openssl",
    action="store_true",
    dest="shared_openssl",
    help="Link to a shared OpenSSl DLL instead of static linking")

parser.add_option("--shared-openssl-includes",
    action="store",
    dest="shared_openssl_includes",
    help="Directory containing OpenSSL header files")

parser.add_option("--shared-openssl-libpath",
    action="store",
    dest="shared_openssl_libpath",
    help="A directory to search for the shared OpenSSL DLLs")

parser.add_option("--shared-openssl-libname",
    action="store",
    dest="shared_openssl_libname",
    help="Alternative lib name to link to (default: 'crypto,ssl')")

# deprecated
parser.add_option("--openssl-use-sys",
    action="store_true",
    dest="shared_openssl",
    help=optparse.SUPPRESS_HELP)

# deprecated
parser.add_option("--openssl-includes",
    action="store",
    dest="shared_openssl_includes",
    help=optparse.SUPPRESS_HELP)

# deprecated
parser.add_option("--openssl-libpath",
    action="store",
    dest="shared_openssl_libpath",
    help=optparse.SUPPRESS_HELP)

parser.add_option("--no-ssl2",
    action="store_true",
    dest="no_ssl2",
    help="Disable OpenSSL v2")

parser.add_option("--shared-zlib",
    action="store_true",
    dest="shared_zlib",
    help="Link to a shared zlib DLL instead of static linking")

parser.add_option("--shared-zlib-includes",
    action="store",
    dest="shared_zlib_includes",
    help="Directory containing zlib header files")

parser.add_option("--shared-zlib-libpath",
    action="store",
    dest="shared_zlib_libpath",
    help="A directory to search for the shared zlib DLL")

parser.add_option("--shared-zlib-libname",
    action="store",
    dest="shared_zlib_libname",
    help="Alternative lib name to link to (default: 'z')")

parser.add_option("--with-dtrace",
    action="store_true",
    dest="with_dtrace",
    help="Build with DTrace (default is true on supported systems)")

parser.add_option("--without-dtrace",
    action="store_true",
    dest="without_dtrace",
    help="Build without DTrace")

parser.add_option("--with-etw",
    action="store_true",
    dest="with_etw",
    help="Build with ETW (default is true on Windows)")

parser.add_option("--without-etw",
    action="store_true",
    dest="without_etw",
    help="Build without ETW")

# CHECKME does this still work with recent releases of V8?
parser.add_option("--gdb",
    action="store_true",
    dest="gdb",
    help="add gdb support")

parser.add_option("--dest-cpu",
    action="store",
    dest="dest_cpu",
    help="CPU architecture to build for. Valid values are: arm, ia32, x64")

parser.add_option("--no-ifaddrs",
    action="store_true",
    dest="no_ifaddrs",
    help="Use on deprecated SunOS systems that do not support ifaddrs.h")

(options, args) = parser.parse_args()


def b(value):
  """Returns the string 'true' if value is truthy, 'false' otherwise."""
  if value:
    return 'true'
  else:
    return 'false'


def pkg_config(pkg):
  cmd = os.popen('pkg-config --libs %s' % pkg, 'r')
  libs = cmd.readline().strip()
  ret = cmd.close()
  if (ret): return None

  cmd = os.popen('pkg-config --cflags %s' % pkg, 'r')
  cflags = cmd.readline().strip()
  ret = cmd.close()
  if (ret): return None

  return (libs, cflags)


def host_arch_cc():
  """Host architecture check using the CC command."""

  try:
    p = subprocess.Popen(CC.split() + ['-dM', '-E', '-'],
                         stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE)
  except OSError:
    print '''Node.js configure error: No acceptable C compiler found!

        Please make sure you have a C compiler installed on your system and/or
        consider adjusting the CC environment variable if you installed
        it in a non-standard prefix.
        '''
    sys.exit()

  p.stdin.write('\n')
  out = p.communicate()[0]

  out = str(out).split('\n')

  k = {}
  for line in out:
    import shlex
    lst = shlex.split(line)
    if len(lst) > 2:
      key = lst[1]
      val = lst[2]
      k[key] = val

  matchup = {
    '__x86_64__'  : 'x64',
    '__i386__'    : 'ia32',
    '__arm__'     : 'arm',
  }

  rtn = 'ia32' # default

  for i in matchup:
    if i in k and k[i] != '0':
      rtn = matchup[i]
      break

  return rtn


def host_arch_win():
  """Host architecture check using environ vars (better way to do this?)"""

  arch = os.environ.get('PROCESSOR_ARCHITECTURE', 'x86')

  matchup = {
    'AMD64'  : 'x64',
    'x86'    : 'ia32',
    'arm'    : 'arm',
  }

  return matchup.get(arch, 'ia32')


def compiler_version():
  try:
    proc = subprocess.Popen(CC.split() + ['--version'], stdout=subprocess.PIPE)
  except WindowsError:
    return (0, False)

  is_clang = 'clang' in proc.communicate()[0].split('\n')[0]

  proc = subprocess.Popen(CC.split() + ['-dumpversion'], stdout=subprocess.PIPE)
  version = tuple(map(int, proc.communicate()[0].split('.')))

  return (version, is_clang)


def configure_node(o):
  # TODO add gdb
  o['variables']['v8_no_strict_aliasing'] = 1 # work around compiler bugs
  o['variables']['node_prefix'] = os.path.expanduser(options.prefix or '')
  o['variables']['node_install_npm'] = b(not options.without_npm)
  o['variables']['node_install_waf'] = b(not options.without_waf)
  o['default_configuration'] = 'Debug' if options.debug else 'Release'

  host_arch = host_arch_win() if os.name == 'nt' else host_arch_cc()
  target_arch = options.dest_cpu or host_arch
  o['variables']['host_arch'] = host_arch
  o['variables']['target_arch'] = target_arch

  # V8 on ARM requires that armv7 is set. CPU Model detected by
  # the presence of __ARM_ARCH_7__ and the like defines in compiler

  if target_arch == 'arm':
    o['variables']['armv7'] = 0 # FIXME

  # clang has always supported -fvisibility=hidden, right?
  cc_version, is_clang = compiler_version()
  if not is_clang and cc_version < (4,0,0):
    o['variables']['visibility'] = ''

  # By default, enable DTrace on SunOS systems. Don't allow it on other
  # systems, since it won't work.  (The MacOS build process is different than
  # SunOS, and we haven't implemented it.)
  if sys.platform.startswith('sunos'):
    o['variables']['node_use_dtrace'] = b(not options.without_dtrace)
  elif b(options.with_dtrace) == 'true':
    raise Exception('DTrace is currently only supported on SunOS systems.')
  else:
    o['variables']['node_use_dtrace'] = 'false'

  if options.no_ifaddrs:
    o['defines'] += ['SUNOS_NO_IFADDRS']

  # By default, enable ETW on Windows.
  if sys.platform.startswith('win32'):
    o['variables']['node_use_etw'] = b(not options.without_etw);
  elif b(options.with_etw) == 'true':
    raise Exception('ETW is only supported on Windows.')
  else:
    o['variables']['node_use_etw'] = 'false'


def configure_libz(o):
  o['variables']['node_shared_zlib'] = b(options.shared_zlib)

  # assume shared_zlib if one of these is set?
  if options.shared_zlib_libpath:
    o['libraries'] += ['-L%s' % options.shared_zlib_libpath]
  if options.shared_zlib_libname:
    o['libraries'] += ['-l%s' % options.shared_zlib_libname]
  elif options.shared_zlib:
    o['libraries'] += ['-lz']
  if options.shared_zlib_includes:
    o['include_dirs'] += [options.shared_zlib_includes]


def configure_v8(o):
  o['variables']['v8_use_snapshot'] = b(not options.without_snapshot)
  o['variables']['node_shared_v8'] = b(options.shared_v8)

  # assume shared_v8 if one of these is set?
  if options.shared_v8_libpath:
    o['libraries'] += ['-L%s' % options.shared_v8_libpath]
  if options.shared_v8_libname:
    o['libraries'] += ['-l%s' % options.shared_v8_libname]
  elif options.shared_v8:
    o['libraries'] += ['-lv8']
  if options.shared_v8_includes:
    o['include_dirs'] += [options.shared_v8_includes]


def configure_openssl(o):
  o['variables']['node_use_openssl'] = b(not options.without_ssl)
  o['variables']['node_shared_openssl'] = b(options.shared_openssl)

  if options.without_ssl:
    return

  if options.no_ssl2:
    o['defines'] += ['OPENSSL_NO_SSL2=1']

  if options.shared_openssl:
    (libs, cflags) = pkg_config('openssl') or ('-lssl -lcrypto', '')

    if options.shared_openssl_libpath:
      o['libraries'] += ['-L%s' % options.shared_openssl_libpath]

    if options.shared_openssl_libname:
      libnames = options.shared_openssl_libname.split(',')
      o['libraries'] += ['-l%s' % s for s in libnames]
    else:
      o['libraries'] += libs.split()

    if options.shared_openssl_includes:
      o['include_dirs'] += [options.shared_openssl_includes]
    else:
      o['cflags'] += cflags.split()


output = {
  'variables': {},
  'include_dirs': [],
  'libraries': [],
  'defines': [],
  'cflags': [],
}

configure_node(output)
configure_libz(output)
configure_v8(output)
configure_openssl(output)

# variables should be a root level element,
# move everything else to target_defaults
variables = output['variables']
del output['variables']
output = {
  'variables': variables,
  'target_defaults': output
}
pprint.pprint(output, indent=2)

def write(filename, data):
  filename = os.path.join(root_dir, filename)
  print "creating ", filename
  f = open(filename, 'w+')
  f.write(data)

write('config.gypi', "# Do not edit. Generated by the configure script.\n" +
  pprint.pformat(output, indent=2) + "\n")

write('config.mk', "# Do not edit. Generated by the configure script.\n" +
  ("BUILDTYPE=%s\n" % ('Debug' if options.debug else 'Release')))

if os.name == 'nt':
  gyp_args = ['-f', 'msvs', '-G', 'msvs_version=2010']
else:
  gyp_args = ['-f', 'make']

subprocess.call([sys.executable, 'tools/gyp_node'] + gyp_args)

Good luck!!

@stolsma
Copy link
Author

stolsma commented Aug 17, 2012

@beriberikix If you follow the new gist it is possible to compile and install Nodejs0.8.7 on your Raspberry Pi. Just forget my previous answer, thats not correct anymore as it is now possible to compile NodeJS0.8.7... ;-)

@originalmind
Copy link

Hi there,
I followed your latest instructions for v0.8.8 apart from apply TooTallNate's patches, as they lead to a 404.
configure, make, make install complete however running 'node' or 'node index.js' outputs the "Illegal instruction" error.

node -v outputs: v0.8.8

I've tried a couple of times to make sure but no luck. I've seen in other instructions that people recommend exporting a few environment variables (CXXFLAGS, etc..) Would there be any other steps to be aware of?

Thanks for your work on this!

@originalmind
Copy link

Still not sure how to get 0.8.8 working but I got 0.8.7 by adapting this guy's script: https://gist.github.com/3245130#gistcomment-399244

@stolsma
Copy link
Author

stolsma commented Aug 29, 2012

@originalmind Oeps, TooTallNate removed his branch so I made my own and referenced it. The receipt above should work again now!!

With the other instructions (exporting variables etc) you get a not working SSL module or a partly working older Raspbian system shared SSL module that is not optimized for use with Node....

@natevw
Copy link

natevw commented Aug 30, 2012

Note that if you're setting this up on a rather fresh SD card (as installing git-core implies) you will need to first configure your git identity — otherwise the patches will not apply! I'd quickly pasted in the full "shell script" above without watching and so the build still failed.

@natevw
Copy link

natevw commented Aug 30, 2012

I can confirm this mostly works. Thanks @stolsma for this post!

It doesn't seem to fix the HTTPS protocol issue for me though:

var f = require('fermata');
f.raw({base:'https://xs4all.nl'}).get(function (e,d) { console.log(e,d); });
// Error: 1074672352:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:../deps/openssl/openssl/ssl/s23_clnt.c:683:

Based on your comment I was under the impression this should work?

@chrisjenx
Copy link

Amazing, Worked perfectly!

@abulte
Copy link

abulte commented Sep 13, 2012

Worked perfectly here too, thanks !

@stolsma
Copy link
Author

stolsma commented Sep 14, 2012

@natevw That HTTPS problem you are seeing is probably related to the use of openssl 1.0.0f in node i.r.t. XS4ALL using 512 bit and higher stuff... They are busy implementing openssl 1.0.1c now in node master so maybe that helps ???

@stolsma
Copy link
Author

stolsma commented Sep 14, 2012

To all: Today the node team landed the two remaining patches on the 0.8.10pre branch! So it looks like that these instructions are not needed anymore when node 0.8.10 is released!!! Yippieajee!!!!!! :-)

I have also updated the patch locations to the node repository for everyone still trying to compile node <0.8.10

@leommoore
Copy link

Thanks Sander, that worked a treat on my pi

@shaunfreeman
Copy link

Thanks mate, worked first time. Nice post

@d0nd3r3k
Copy link

Install it in 5 min, no need to compile no more :)

wget http://nodejs.org/dist/v0.10.2/node-v0.10.2-linux-arm-pi.tar.gz
tar xvzf node-v0.10.2-linux-arm-pi.tar.gz

you only need to add it to your path variables here:

nano /etc/profile

Check https://github.com/DonaldDerek/rPi-cheat-sheet for more info..

@antonywu
Copy link

@DonaldDerek, thanks!

I didn't have to add any path. I only had to symbolic link node and npm to be under /usr/bin
sudo ln -s ./node-v0.10.2-linux-arm-pi/bin/node /usr/bin/node sudo ln -s ./node-v0.10.2-linux-arm-pi/bin/npm /usr/bin/npm

@sgerrand
Copy link

sgerrand commented Jan 9, 2015

@DonaldDerek et al who come here in search of a ARM binary:

For whatever reason, there is no longer an ARM binary available on the node.js distribution site. 😾

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment