quarkdown icon indicating copy to clipboard operation
quarkdown copied to clipboard

Install PDF dependencies without root privileges

Open alerque opened this issue 6 months ago • 13 comments

I've been trying to setup packaging for Arch Linux (currently here in the AUR), but there is at least one major issue. It seems there is no way to build and package all the dependencies ahead of time. I have the Java stuff worked out to compile to HTML output, but trying to generate a PDF results in something like this:

$ quarkdown c foo.qmd --pdf
<p>foo bar</p><div class="page-break" data-hidden=""></div><h1 id="things">things</h1><ul><li>a list</li><li>of stuff</li></ul>
Exception in thread "main" java.lang.IllegalStateException: Command failed with non-zero exit code:
npm error code EACCES
npm error syscall symlink
npm error path ../lib/node_modules/puppeteer/lib/cjs/puppeteer/node/cli.js
npm error dest /usr/bin/puppeteer
npm error errno -13
npm error Error: EACCES: permission denied, symlink '../lib/node_modules/puppeteer/lib/cjs/puppeteer/node/cli.js' -> '/usr/bin/puppeteer'
npm error     at async symlink (node:internal/fs/promises:1004:10)
npm error     at async Promise.all (index 0)
npm error     at async Promise.all (index 0)
npm error     at async #createBinLinks (/usr/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arborist/rebuild.js:396:5)
npm error     at async Promise.allSettled (index 0)
npm error     at async #linkAllBins (/usr/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arborist/rebuild.js:377:5)
npm error     at async #build (/usr/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arborist/rebuild.js:162:7)
npm error     at async Arborist.rebuild (/usr/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arborist/rebuild.js:62:5)
npm error     at async [reifyPackages] (/usr/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js:336:11)
npm error     at async Arborist.reify (/usr/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js:141:5) {
npm error   errno: -13,
npm error   code: 'EACCES',
npm error   syscall: 'symlink',
npm error   path: '../lib/node_modules/puppeteer/lib/cjs/puppeteer/node/cli.js',
npm error   dest: '/usr/bin/puppeteer'
npm error }
npm error
npm error The operation was rejected by your operating system.
npm error It is likely you do not have the permissions to access this file as the current user
npm error
npm error If you believe this might be a permissions issue, please double-check the
npm error permissions of the file and its containing directories, or try running
npm error the command again as root/Administrator.

This is the Java app somewhere trying to fetch and install NPM packages globally at runtime! This is absolutely a non-starter for distributions to package something like this. Somehow it needs to be re-arranged so that either the system can supply the dependencies directly (best) or the dependencies can be fetched/assembled/compiled ahead of time and placed in a vendored directory of some sort where the app can use them having already been installed by the distro package manager.

alerque avatar Jun 10 '25 09:06 alerque

This has significant overlap with #54.

alerque avatar Jun 10 '25 09:06 alerque

How do you suggest packaging Puppeteer with the distributable file? @alerque

iamgio avatar Jun 10 '25 09:06 iamgio

I would suggest not. Move the npm install stuff behind some sort of command flag (quarkdown install or something like that) and never try to do it automatically when compiling a document. When the user invokes the compile command just assume puppeteer is in the user's $PATH somewhere, that way the system package can just provide it via a dependency that will have it at /usr/bin/puppeteer or wherever is appropriate.

A not-so-nice but workable alternative is provide a way at build time (i.e. next to the gradle tooling) to run the npm install with a specific target argument that generates the node_modules directory you need, and expect that to be available in quarkdown's lib directory or something like that.

Either way quarkdown cannot assume being able to ever write to its own directory location as that will be owned by the system, not the user running it.

alerque avatar Jun 10 '25 09:06 alerque

Move the npm install stuff behind some sort of command flag (quarkdown install or something like that)

Absolutely reasonable, I will do it so that it covers #54 too. Root permissions will still be required for the installation though.

I believe shipping all the tooling with the distributable file would be the best solution, ux-wise. But with Puppeteer being ~300MB, compared to Quarkdown's ~25MB, this is unlikely to happen.

Puppeteer's size is also the reason why I went for installing the NPM package globally instead of locally. It's going to require root permissions, but at least there can be a single Puppeteer installation for multiple Quarkdown projects.

iamgio avatar Jun 10 '25 09:06 iamgio

@alerque could you please test if, after installing Puppeteer by running Quarkdown as root, subsequent PDF compilations run normally without root permission?

iamgio avatar Jun 10 '25 09:06 iamgio

Running once as root is a non-starter for distro packages. We must be able to generate all the things that need to be installed as root at package build time. Once the package is built the package manager will take care of placing all the things that need to be places as root, no user apps are allowed to install things into the global system at runtime, not even once.

alerque avatar Jun 10 '25 09:06 alerque

Just found out (my bad, not an NPM expert) about the --prefix flag that will let Quarkdown install packages globally to a non-restricted location (e.g. <userhome>/.quarkdown or similar). This will fix #54.

@alerque as a last feedback before getting to work: with this enhancement applied, do you still think it is unacceptable to install the package during the first compilation? I'm afraid forcing the user to go for a mandatory install phase would result in a bad ux.

iamgio avatar Jun 10 '25 10:06 iamgio

Using the --prefix will help create distro packages as long as we can run it at build time with a special path of the place where the package is being assembled, e.g. $BUILDROOT/usr/lib or whatever it is, and then at runtime the app can find it at /usr/lib/. If the actual prefix gets baked in as the place it needs to be that makes it hard to generate packages.

As to the second part, yes I absolutely do think that apps should never try to fetch or install things at runtime, an first run or otherwise. At the very least prompt for confirmation, but better yet just redirect them to the install command.

BTW I'm still hoping not to include puppeteer in the packaging at all and just depend on it, so a runtime check for puppeteer in the system path should be all that is needed for packaging purposes.

alerque avatar Jun 10 '25 10:06 alerque

Looking a little bit at the sources (not exhaustively) it looks like you aren't even using the puppeteer CLI at all (which is fine), you just need the puppeteer-core library available to call, no? So I suppose the actual dependencies will end up being nodejs + puppeteer-core, no?

alerque avatar Jun 10 '25 11:06 alerque

the actual dependencies will end up being nodejs + puppeteer-core

puppeteer bundles a headless distribution of Chrome, granting consistency across PDF files. puppeteer-core doesn't, as it just exposes the API. It's unlikely to assume all users have (a modern version of) Chrome installed.

iamgio avatar Jun 10 '25 11:06 iamgio

It's unlikely to assume all users have (a modern version of) Chrome installed.

In the context of distro packaging, I can guarantee that a modern Chrome is installed. Arch Linux is a rolling release distro, meaning only the latest version of all current packages is a supported state. The same will be true for almost any distro, even not rolling release ones. If the relevant quarkdown package depends on puppeteer and a browser, those dependencies will be current—or at least of the same era as the quarkdown version. For Arch if I make the package depend on a browser it will be the latest build of that at the time of installation.

In any case that variable shouldn't bee too relevant to you, as long as you assume the system can/is providing puppeteer and a matching current headless chrome to go with it.

alerque avatar Jun 10 '25 12:06 alerque

@alerque in #71 I have deleted any way of installing NPM packages, as you're right: it should be a responsibility of the user/pm to provide the required dependencies. In place of the installation, now there is an explanatory error message.

That said, I'm afraid it's going to be an unpleasant change for the base user. That's why I'm going to postpone merging until I can provide installation via brew/winget to provide the required dependencies out of the box. I hope in a few days.

I'm open to feedback.

iamgio avatar Jun 10 '25 13:06 iamgio

#84 should be a good starting point to distribute Quarkdown on package managers, so I can finally merge the PR.

iamgio avatar Jun 14 '25 12:06 iamgio

@alerque starting from v1.6.0, Quarkdown is totally package-manager-friendly, as no additional downloads or permissions will be required after the very first installation.

iamgio avatar Jun 23 '25 09:06 iamgio