F43 Change Proposal: Use update-alternatives for managing NodeJS symlinks (self contained)

Use update-alternatives for managing NodeJS symlinks

This is a proposed Change for Fedora Linux.
This document represents a proposed Change. As part of the Changes process, proposals are publicly announced in order to receive community feedback. This proposal will only be implemented if approved by the Fedora Engineering Steering Committee.

Wiki
Announced

:link: Summary

We aim to move away from manual management of /usr/bin/node, /usr/bin/npm, and similar symlinks to leveraging update-alternatives system.

:link: Owners

:link: Detailed Description

This is a part of a larger iteration in a way we package NodeJS for Fedora and RHEL. The other parts are Changes/NodejsNodeModulesPath and Changes/NodeJSMetapackages. This change deals specifically with the management of the non-versioned symlinks in system paths.

Currently, the NodeJS packages (streams) provided in Fedora are all installable in parallel, by virtue of moving any potentially conflicting bits into versioned equivalents and/or versioned directories. For one example of many:

%install mv %{buildroot}%{_bindir}/node %{buildroot}%{_bindir}/node-%{node_version_major} …

One of the streams is designed as the “default” one, and that stream then ships manually created non-versioned symlinks to the renamed paths:

%if 0%{nodejs_is_default} ln -srf %{buildroot}%{_bindir}/node-%{node_version_major} %{buildroot}%{_bindir}/node … %endif

By using update-alternatives, we can iterate on this idea and gain several benefits outlined below.

:link: Feedback

:link: Benefit to Fedora

  1. No matter which stream you install, you’ll always have /usr/bin/node and other non-versioned names available. The versioned names will be also present, if you want to be specific in your scripts.
  2. There is no “official Fedora endorsement” on which NodeJS stream should be “best” or “default”; if you as a system administrator have multiple streams installed, the decision of which should be the default is up to you.

The second item will also give us the maintainers greater freedom in introducing new streams and obsoleting the old ones without worrying too much about how to switch the “default” in the middle of the distribution life cycle. This is not a big pain point for Fedora, where the length of the life of a single version matches the length of upstream NodeJS LTO support pretty closely, but becomes much more important in longer-living downstream distributions (e.g. RHEL).

:link: Scope

  • Proposal owners:
    • Port the manual symlink creation and management to update-alternatives.
    • Ensure the streams behave consistently and no conflict is introduced.

:link: User Experience

  • Users will be able to install any number of streams and switch between default one rather easy by just running `update-alternatives --config node’.

:link: Dependencies

  • Should not cause any problems to dependant packages.

:link: Contingency Plan

  • Contingency mechanism: Revert back to manual symlink management and go back to the design phase.
  • Contingency deadline: Beta Freeze.
  • Blocks release? We aim to do the rebuilds in Koji side tag and merge it atomically; so NO.

:link: Documentation

:link: Release Notes

TODO

Last edited by @amoloney 2025-07-10T11:27:47Z

Last edited by @amoloney 2025-07-10T11:27:47Z

How do you feel about the proposal as written?

  • Strongly in favor
  • In favor, with reservations
  • Neutral
  • Opposed, but could be convinced
  • Strongly opposed
0 voters

If you are in favor but have reservations, or are opposed but something could change your mind, please explain in a reply.

We want everyone to be heard, but many posts repeating the same thing actually makes that harder. If you have something new to say, please say it. If, instead, you find someone has already covered what you’d like to express, please simply give that post a :heart: instead of reiterating. You can even do this by email, by replying with the heart emoji or just “+1”. This will make long topics easier to follow.

Please note that this is an advisory “straw poll” meant to gauge sentiment. It isn’t a vote or a scientific survey. See About the Change Proposals category for more about the Change Process and moderation policy.

IMO we should be using alternatives less, not more :frowning:

They’re old, creaky, and often don’t work as expected. And using them more is also counter to the general trend of trying to get rid of any usages of RPM scriptlets we can …

Do you think it would be possible to implement swappable defaults using RPM mechanisms instead?

3 Likes

My first instinctual response was “I do not know of any rpm mechanism that could do that” and asking for help.

However, Vitaly Zaitsev in the mail thread suggested introducing -bin packages (e.g. nodejs22-bin) that would contain the symlinks, (presumably) conflict with each other, and rely on dnf swap to switch between the streams (e.g. dnf swap nodejs22-bin nodejs24-bin). Reading more in detail on weak dependencies (“dnf will by default remove them from transaction in case of conflicts” (source), this now looks doable.

I’ll update the wiki page with the feedback received, and we’ll experiment with this approach.

1 Like

This change proposal has now been submitted to FESCo with ticket #3452 for voting.

To find out more, please visit our Changes Policy documentation.

Some updates has happened on the Wiki page; it is now rewritten with having a swappable -bin packages to solve the problem. There is also a link to testing COPR, the corresponding sources and some testing scenarios we have come up so far.

In the current state, there are 2 -bin packages for each nodejs stream (and I’ll be using nodejs24 as an example): nodejs24-bin and nodejs24-npm-bin. nodejs24-bin provides only /usr/bin/node symlink, and nodejs24-npm-bin contains /usr/bin/npm and /usr/bin/npx symlinks. The split is there to keep the nodejs24-npm subpackage semi-independent from nodejs24, so you can uninstall/not install it and still have the benefit of /usr/bin/node and no broken symlinks left on system. :wink:

On the flip side, this necessitates adding --allowerasing to the dnf swap command, to ensure both of the -bin packages are removed and swapped:

# dnf swap nodejs24-bin nodejs22-bin
Updating and loading repositories:
Repositories loaded.
Failed to resolve the transaction:
Problem: problem with installed package
  - package nodejs24-npm-bin-1:24.4.1-7.fc43.noarch from copr:copr.fedorainfracloud.org:jstanek:nodejs-bin-alternatives requires nodejs24-bin = 1:24.4.1-7.fc43, but none of the providers can be installed
  - package nodejs24-npm-bin-1:24.4.1-6.fc43.noarch from copr:copr.fedorainfracloud.org:jstanek:nodejs-bin-alternatives requires nodejs24-bin = 1:24.4.1-6.fc43, but none of the providers can be installed
  - installed package nodejs24-npm-bin-1:24.4.1-8.fc43.noarch requires nodejs24-bin = 1:24.4.1-8.fc43, but none of the providers can be installed
  - package nodejs24-npm-bin-1:24.4.1-8.fc43.noarch from copr:copr.fedorainfracloud.org:jstanek:nodejs-bin-alternatives requires nodejs24-bin = 1:24.4.1-8.fc43, but none of the providers can be installed
  - conflicting requests

This is annoying, but the only alternative we could think of is to uncouple the nodejs24-bin and nodejs24-npm-bin and having them being independent from one another. However, this could lead to potentially very confusing scenarios where e.g. /usr/bin/node -> node-22 while /usr/bin/npm -> npm-24, so that approach was not pursued.


There was also the question of what happens on upgrades between Fedora versions. Since we do not generally obsolete EOL NodeJS streams, they can stay on the system indefinitely; and with them, their -bin packages as well. With the current setup, without user intervention and their dnf swap, that could mean that /usr/bin/node will keep pointing to long-obsolete stream even when a supported one is installed later. I see no easy way out of this.

One approach would be to start adding the EOL streams to fedora-obsolete-packages, which would remove the entire stream from the system. That can then lead to removing the symlinks from the system entirely – if there was already a newer stream installed, it’s -bin packages won’t be pulled in automatically. IIUC this is one of the situations update-alternatives would handled better – if you remove the currently selected alternative, it should automatically switch to the next best available one.

We can also play with adding Obsoletes of the -bin packages and forcing their update when the stream is obsoleted somehow. I have not yet experimented with this idea, but it feels error prone on the maintenance side.

Not a great fan of either of those. Let me know what you think.

After writing the previous comment, it occurs to me that with the current approach, we are essentially saying both “We are installing some -bin packages for you, don’t worry about them” (since they are Recommends: from the core ones) and “You now need to take care of them” (to ensure they did not dissapear, point to right places, etc.).

So another approach could be to make these packages specifically opt-in: They will not be pulled as weak dependencies (or maybe as very weak dependencies), the user will have to select them on installation explicitly. Then we can assume they are theirs responsibility.

To keep the hands-off approach as well, we could then use the proposed metapackage to pull the -bin packages as well, but only if the metapackage is used. That should also result in a cleaner upgrade path, as when we switch the stream the metapackage pulls in, the old one should be removed as no-longer-needed dependency in the same transaction. WDYT?