<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Linkwarden 1.21.1: monolith snapshots fail with spawn monolith EACCES]]></title><description><![CDATA[<p dir="auto">Ahoi! Ive run into an issue setting up linkwarden. Looked like no local copies of links could be created. I investigated with AI and found a permission issue. Tested the fix locally and it works. Here is the full report and patch (by Claude Opus4.7):</p>
<h2>Symptom</h2>
<p dir="auto">For every bookmark you save, Linkwarden's worker creates four offline snapshots in the background and stores their paths on the link record: <code>image</code> (PNG screenshot), <code>pdf</code>, <code>readable</code> (extracted plain-text/JSON), and <code>monolith</code> (full self-contained HTML archive — the actual offline copy of the page, with images/CSS/JS inlined as <code>data:</code> URIs). When a snapshot fails the corresponding field is set to the literal string <code>"unavailable"</code>.</p>
<p dir="auto">On <code>app.linkwarden.cloudronapp@1.21.1</code> the first three always succeed, but <strong>every</strong> link ends up with <code>monolith: "unavailable"</code>:</p>
<pre><code class="language-json">GET /api/v1/links/14
{
  "id": 14,
  "url": "https://example.com/",
  "image":    "archives/1/14.jpeg",
  "pdf":      "archives/1/14.pdf",
  "readable": "archives/1/14_readability.json",
  "monolith": "unavailable",
  "lastPreserved": "2026-04-30T12:41:57.614Z"
}
</code></pre>
<p dir="auto">In effect, the most useful preservation format — the offline HTML archive — is missing from every saved bookmark, while the UI just shows "unavailable" without surfacing why.</p>
<p dir="auto">Worker logs show the underlying error:</p>
<pre><code>Error: spawn monolith EACCES
</code></pre>
<p dir="auto">Source: <a href="https://github.com/linkwarden/linkwarden/blob/v2.14.1/apps/worker/lib/preservationScheme/handleMonolith.ts" target="_blank" rel="noopener noreferrer nofollow ugc"><code>apps/worker/lib/preservationScheme/handleMonolith.ts</code></a> calls <code>spawn("monolith", …)</code>, which relies on the binary being resolvable through <code>PATH</code> for the worker process.</p>
<h2>Root cause</h2>
<p dir="auto">The <code>monolith</code> binary is installed via <code>cargo install --locked monolith</code> in the <code>Dockerfile</code>. Cargo defaults to <code>$CARGO_HOME/bin</code>, which during the build is <code>/root/.cargo/bin</code>. The base image keeps <code>/root</code> at mode <code>700</code>, and the runtime worker runs as <code>cloudron</code> (UID 1000) — so even though the binary itself is <code>-rwxr-xr-x</code>, <code>cloudron</code> can't traverse <code>/root</code> to reach it.</p>
<p dir="auto">Reproduce inside a 1.21.1 container:</p>
<pre><code>$ ls -ld /root
drwx------ 1 root root 4096 Apr 23 02:20 /root

$ ls -la /root/.cargo/bin/monolith
-rwxr-xr-x 1 root root 12384776 Apr 23 02:22 /root/.cargo/bin/monolith

$ runuser -u cloudron -- /root/.cargo/bin/monolith --version
runuser: failed to execute /root/.cargo/bin/monolith: Permission denied
</code></pre>
<p dir="auto"><code>/root</code> is on the read-only image layer, so <code>chmod o+x /root</code> at runtime is also rejected. This is purely a packaging issue — the binary is fine, just unreachable.</p>
<h2>Fix</h2>
<p dir="auto">One-line change in the <code>Dockerfile</code>: pass <code>--root /usr/local</code> to <code>cargo install</code> so the binary lands in <code>/usr/local/bin/monolith</code>, which is on the default <code>PATH</code> and traversable for everyone.</p>
<pre><code class="language-diff">-# install rust and monolith (/root/.cargo/bin/monolith)
+# install rust and monolith
+# install monolith into /usr/local/bin so it is reachable for the runtime
+# `cloudron` user (UID 1000); /root is mode 700 in the base image and would
+# otherwise hide /root/.cargo/bin/monolith → spawn EACCES in the worker.
 RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
 ENV PATH="/root/.cargo/bin:${PATH}"
-RUN cargo install --locked monolith
+RUN cargo install --locked --root /usr/local monolith
</code></pre>
<p dir="auto">Full patch attached: <strong><code>linkwarden-monolith-eacces.patch</code></strong> (3 files, +9/−3 — Dockerfile + CHANGELOG entry + CloudronManifest version bump 1.21.1 → 1.21.2).</p>
<p dir="auto">I'd happily open a merge request directly, but I don't have a <code>git.cloudron.io</code> account — feel free to apply the patch with <code>git am linkwarden-monolith-eacces.patch</code> (or just take the diff).</p>
<h2>Verification</h2>
<p dir="auto">Verified the fix on a running 1.21.1 container by approximating the post-patch state at runtime:</p>
<ol>
<li><code>cp /root/.cargo/bin/monolith /app/data/bin/monolith</code> (writable persistent volume)</li>
<li>Prepend <code>/app/data/bin</code> to the worker's <code>PATH</code> via a tiny <code>NODE_OPTIONS=--require …</code> shim (necessary because <code>dotenv-cli</code> won't overwrite an already-set <code>PATH</code>)</li>
<li>Restart the app</li>
</ol>
<p dir="auto">After that, a freshly created link preserves correctly:</p>
<pre><code class="language-json">{
  "id": 15,
  "url": "https://en.wikipedia.org/wiki/Monolith_(file_archive)",
  "monolith": "archives/1/15.html",
  "image":    "archives/1/15.jpeg",
  "pdf":      "archives/1/15.pdf",
  "readable": "archives/1/15_readability.json"
}
</code></pre>
<p dir="auto">The resulting HTML is 600 KB and embeds data URIs as expected from monolith. Pre-existing "unavailable" links can be brought back with the <code>Re-preserve all broken</code> admin action.</p>
<h2>Possibly related</h2>
<p dir="auto">Topic <a href="https://forum.cloudron.io/topic/13640">#13640</a> ("Missing HTML saved version and cookie popup in screenshots") describes the same observable symptom and was tentatively diagnosed as a resource issue. Based on the reproduction above I believe this <code>EACCES</code> is the actual underlying cause for the missing HTML snapshots.</p>
]]></description><link>https://forum.cloudron.io/topic/15464/linkwarden-1.21.1-monolith-snapshots-fail-with-spawn-monolith-eacces</link><generator>RSS for Node</generator><lastBuildDate>Thu, 30 Apr 2026 22:52:46 GMT</lastBuildDate><atom:link href="https://forum.cloudron.io/topic/15464.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 30 Apr 2026 13:15:46 GMT</pubDate><ttl>60</ttl></channel></rss>