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?

Introducing a breaking change like this to users where the binary names they expect, for good reason, are in a separate -bin package is breaking and hindering the user experience for gain I simply cannot see outweighing how poorly that will make people using NPM feel about this.

If it was done like LLVM and GCC where only the default version has unnumbered binaries, I would feel differently. But as it exists this is an incredibly hindering change.

To put this into perspective, imagine you didn’t install the metapackage, you simply installed nodejs-npm to work on your project. Running npm doesn’t work. You aren’t quite familiar enough with dnf to know dnf rq --whatprovides /usr/bin/npm (most people I know are not). You see in the repos these -bin packages, but your understanding of this comes from how other distributions use this term (i.e., Arch, etc.), so this sounds like a prebuilt but packaged version of NPM and not like it would solve your issue. Additionally, even if you figured out the binary you wanted was npm-22, your project’s package.json file has a script called build:prod which calls, let’s say for example, vite && npm run build. The second part calls npm directly.

This also breaks all current guides for using these tools on Fedora. And it cannot be understated just how many guides, both general and project specific, exist for using NPM both for Fedora and for general use.

I imagine most people would understandably come to the conclusion something is wrong with NPM on Fedora or that it is very troublesome to work with.

2 Likes

To put this into perspective, imagine you didn’t install the metapackage, you simply installed nodejs-npm to work on your project.

In the default settings, just dnf install nodejs-npm will also install nodejs-npm-bin for you, so no matter the stream, running just npm should continue to work. And the existence of other -bin packages gives you the option to change which npm is used via the dnf.

Issues may rise in environments where optional dependencies are not enabled (minimal containers, the koji build system, etc.) and there you have to be explicit that you also want the npm command itself. It’s not a perfect solution, unfortunately.

As for the naming of the symlink packages, yeah, it’s perhaps a bit unfortunate. Suggestions for better names are welcome.

I think we can do this:

  • The nodejs-npm package (and also other versions) can Requires: npm-bin-choice
  • The -bin package should provides that requires
  • The nodejs-npm packages should suggests the bin packages
  • Add a npm-no-bin package that provides npm-bin-choice. As the name suggests, no binary would be provided.

So now we can make sure even if optional dependencies are not enabled we get the binary by default.

As @jstanek said, the separate -bin packages are automatically installed on user systems unless weak dependencies are explicitly disabled. dnf install nodejs or dnf install nodejs-npm should still work in these cases and running plain npm or node still works. So I don’t think this is as a big an issue as you make it out to be. Still, I agree it can be improved. See the proposal in my next post.

Ideally, this would make things better for node users. They can choose between different packaged versions of node. Other users who just need a single version of node and npm should still be able to run dnf install nodejs-npm and still get a working setup and need not concern themselves with the way the subpackages are laid out. This does not seem like a huge crisis to me.

1 Like

Background

Here is my current understanding of the packaging situation. @jstanek, please correct me if I’m wrong.

  1. nodejs20, nodejs22, and nodejs24 each Recommend their respective versioned nodejsNN-bin package which each contain the /usr/bin/nodejs symlink.
  2. Each nodejsNN-bin package Provides alternative-for(nodejs-bin)
  3. Basically the same pattern exists for nodejsNN-npm and nodejsNN-npm-bin that Provides alternative-for(nodejs-npm-bin) and contains /usr/bin/npm.
  4. There is no hard dependency (Requires) that ensures the unversioned symlinks are present when any nodejs or npm package is installed. This creates problems in the buildsystem and elsewhere where weak deps are disabled as shown in https://pagure.io/fesco/issue/3567 and https://lists.fedoraproject.org/archives/list/devel@lists.fedoraproject.org/thread/QDIYQTGCE46TMYYTCMF54A2HLNEGLCOF/#QDIYQTGCE46TMYYTCMF54A2HLNEGLCOF. dnf install nodejs or dnf install nodejs-npm only creates a functional setup with both /usr/bin/node and /usr/bin/npm on user systems where weak dependencies are enabled.

Proposal

I would suggest that each nodejsNN package adds Requires: alternative-for(nodejs-bin) and likewise that each nodejsNN-npm package adds Requires: alternative-for(nodejs-npm-bin). This would make sure that /usr/bin/node must exist when any nodejsNN package is installed and that /usr/bin/npm must exist when any nodejsNN-npm is installed even in the buildsystem or other environments where weak deps are disabled.

The existing Recommends (see 1. in above list) would make sure that the matching versioned nodejsNN-bin package is preferred to fulfill the requirement unless a different version nodejsNN-bin package is already installed. Likewise for matching nodejsNN-npm and nodejsNN-npm-bin.

To summarize:

  1. No new package conflicts are introduced by this approach.
  2. The intentions of the Change should be preserved.
  3. The behavior for both users and packagers building in mock, koji, etc. should be more predictable.
2 Likes

in fesco#3567 we specified the following 3 conditions:

  • all nodejs*-npm packages should provide the npm or npm-* binaries.
    • satisfied (since it’s a harddep now)
  • all misleading -bin subpackages should be removed.
    • not yet satisfied
  • The default nodejs-npm package must provide the default npm binary.
    • satisfied

While this feels like bikeshedding at first glance, we still believe the naming of the subpackage can be improved in order to reduce confusion, but otherwise I think gotmax23’s proposal is doable.

Maybe we can look at how python is naming their symlink packages? Not so sure.

Matched fields: name 
 python-unversioned-command.noarch      The "python" command that runs Python 3
1 Like

This is exactly the solution I had in mind in the fesco ticket. Except I would use /usr/bin/node as the Requires as I find it more understandable when inspecting the requires.

In fact, I wanted to send a proof of concept PR, but it was rather late here, so I didn’t get to it. I still plan to do that today.

2 Likes

Sounds good to me!

You have understood it correctly. :+1: The new Requires: (resp. those proposed by mhroncok below, which are being tested as I’m writing this) looks like the missing piece of the puzzle. Thank you both for the suggestions and PR.

in fesco#3567 we specified the following 3 conditions

BEGIN rant

I’m definitely a “bit” emotional in here, and I apologize in advance. We’ve tried to share our progress and our testing repos multiple times on various channels (change proposals, ML messages, responding to issues, etc.), and we worked hard to incorporate any feedback received – e.g. the -bin packages came from one such suggestion. (More on that later.)

Yes, the proposal is imperfect, and thus break things, and we are sorry for that. Nevertheless, I would personally appreciate if the proponents of the Fesco ticket had reached us directly with their concerns and tried to work with us on them, instead of going directly to packaging committee and request them to reject the entire change wholesale until we can somehow figure out how to make it perfect.

END rant

all nodejs*-npm packages should provide the npm or npm-* binaries.

  • satisfied (since it’s a harddep now)

That formulation has confused me somehow, since I read “provides” as “contains”/”ships”. Glad to hear that hard dependency is enough.

all misleading -bin subpackages should be removed.

IIUC, the issue is that other distributions have the -bin suffix reserved for packages containing pre-built stuff not originating from that distro’s build system. I’m not aware of any such reservation in Fedora (correct me if I’m wrong).

I agree with the sentiment, and I’m willing to rename these packages and sooner rather than later. Please bikeshed better names. :wink: Alternatively, if no one objects, let’s go with the -unversioned-command suffix that is/was already in use by the python folks.

And if we are doing this, please propose an update to Fedora Packaging Guidelines – if we agree that the -bin suffix should not be used, or used only for specific purpose, it should be documented there.

1 Like

in fesco#3567 we specified the following 3 conditions

For what it’s worth, I don’t think posting a list of conditions + signatures and a strictly worded FESCo ticket is a productive way to participate in the Changes Process. I understand that sometimes we don’t find out about issues until later in the development cycle and that can create urgency, but the tone of this proposal made me as a community member hesitant to participate in the discussion and propose an alternative.

And the node maintainers did a good job here. Other approaches that have been tried for handling versioned language stacks (SCLs, Modularity :ghost: :ghost:, Alternatives) have various shortcomings. The maintainers incorporated feedback and worked to come up with a better solution for node using native RPM features that works well now that we made one small addition.

Right, pre-built content is banned from the official Fedora buildsystem and repos (other than for firmware), and AFAIK we do not have any Naming Guidelines about the -bin suffix. I think it’s mainly an Archlinux thing? There are a few other packages in Fedora (list) that use the -bin suffix to mean a subpackage that includes command line tools or compiled binaries.

Regardless, I agree it’d be good to standardize on -unversioned-command for compat symlinks if possible, as it’s more descriptive and matches Python’s prior art.

As one of the people who signed off on the ticket, I would like to apologize for the tone coming off harsh or accusatory in any way. This was not the intent of me or anyone else involved.

I myself had attempted to reach out here, previously. Due to how Discussions threads can get buried, that 44 had already branched, and that this was already breaking CIs (I and other developers/packagers had hit this, hence my reaching out), there was a sense of urgency and the idea for a formal request to FESCo was weighed and ultimately decided upon.

The FESCo ticket, while I can see where it maybe came off harsh, was intended to create the sort of dialogue that is being had now, but more urgently and potentially with more prominent members of Fedora present and aware of any issues. While reverting was the goal, especially due to urgency, a solution similar to decathorpe’s comment (going back to the drawing board and discussing further) is acceptable, and the discussion seems to have brought about a decent compromise given this urgency.

I, personally, would accept the proposed changes. I think the hard requirement, the ability to still install a nodejsXX-npm package and have a working environment work as intended, and the name change to better reflect other similar packages in Fedora (npmXX-unversioned-command) are all good. These resolve a lot of the development and user concerns I had experienced or had been brought to my attention.

I suppose my final concern (more a question) would be this: nodejs and nodejs-npm presently pull in all necessary dependencies for a fully working NPM setup (including in CIs), would this behavior be retained with these proposed changes?

Additionally, myself and colleagues from Fyra Labs will be attending the FESCo meeting on Tuesday which is supposed to discuss this further. Since a lot of us signed off on the ticket to revert this change, we definitely want to be part of the discussion going forward and work together on a solution that will benefit everyone as much as possible.