Keeping systemctl node version in sync with NVM

I am recently going through a ghost major version update with my Ubuntu 18 server and I keep running into an issue where the node version I use via nvm on the server is out of sync with the node version being run by systemctl when I run ghost update.

In my server file, ghost_www-website-com.service the ExecStart references the /usr/bin/node path, which contains an old node version installed to the server when I first created it, but since then I have moved to use nvm to manage the node on the system.

I know I can change the path from usr/bin/node to /home/ghost-mgr/.nvm/versions/node/v14.15.0/bin/node to execute node from my nvm directory, but this isn’t helpful if I update to a new node version via nvm. Is there a way to have a dynamic path of the node version selected with nvm that systemctl understands?

ghost_www-website-com.service config:

[Service]
Type=simple
WorkingDirectory=/var/www/ghost
User=999
Environment="NODE_ENV=production"
ExecStart=/home/ghost-mgr/.nvm/versions/node/v14.17.0/bin/node /usr/bin/ghost run
Restart=always
1 Like

Same issue - I switched to nvm to avoid reliance on root, and it comes in handy anyway because latest ghost requires <latest node. But now both /usr/bin/node and /usr/bin/ghost are wrong.

Do I really need to hardcode them in the service config?

Not finding much guidance here, but I stumbled on nvm-exec which seems like it might be for this purpose. You create a .nvmrc in the “project” folder with the version you want to use, and nvm-exec will load the correct nvm environment for you and then exec its argument.

So I did nvm current > .nvmrc in my ghost directory, then set ExecStart to:

ExecStart=/home/user/.nvm/nvm-exec ghost run

This works when starting the service directly. Alas, ghost doctor (and even ghost start) bork because they can’t determine the version of node used from that ExecStart.

After much prying, I see that’s because doctor passes --version to the first token in ExecStart:

But nvm-exec doesn’t support --version, so I’m a bit stuck.

Okay, after way longer than I care to admit, I have a least-worst workaround.

Tricking the problematic split() by using non-space delimiters doesn’t work, because execa chokes on delimiters anyway. Even a wrapper doesn’t work, because node doesn’t understand ghost but nvm-exec does.

So finally, I made a tweak to nvm-exec. I called mine nvm-exec-or-node just to preserve the original. It’s the same, except the last line (the actual exec) is replaced with this:

# If called with no arguments, or with a flag instead of an executable, exec
# node by default. This allows users like `ghost doctor` to pass --version as
# if nvm-exec was actually node.
if [[ "$#" -eq 0 ]] || [[ $1 == -* ]]; then
  exec node "$@"
else
  exec "$@"
fi

And my ExecStart then simply becomes:

ExecStart=/home/user/.nvm/nvm-exec-or-node ghost run

When ghost doctor runs, it’s as if its passing --version to node, so it’s happy. And when ghost start runs, systemd uses the normal nvm-exec functionality, so it’s happy.

Are we all happy now?