Document npm registry endpoints and auth gotchas
All checks were successful
continuous-integration/drone/push Build is passing

Add the exact pip/npm registry endpoints and secret names, the
scope-scoped (not global) registry requirement given Verdaccio's missing
uplink, the host-scoped _auth rule, the per-item Drone shell caveat, and
the Verdaccio storage uid/permissions gotcha behind the confusing
"404 no such package available" error on publish.
This commit is contained in:
2026-10-21 01:31:07 +05:30
parent da1e46a921
commit 322741e00c
7 changed files with 161 additions and 7 deletions

View File

@@ -753,8 +753,13 @@ pipelines</strong>:</p>
the disaster-recovery / replay path.</li>
</ol>
<p>Secrets: <code>NPM_REPO_URL</code>, <code>NPM_USERNAME</code>, <code>NPM_PASSWORD</code>. Auth is a generated
<code>.npmrc</code> (<code>registry=</code> + <code>_auth=</code> base64 of <code>user:pass</code>) written from secrets at
runtime — the committed <code>.npmrc</code> in a repo only points at the registry URL.</p>
<code>.npmrc</code> written from secrets at runtime. It sets a <strong>scope-scoped</strong> registry,
never a global one — <code>@aetoskia:registry=$NPM_REPO_URL</code> — plus a host-scoped
auth line <code>//&lt;host&gt;/:_auth=&lt;base64(user:pass)&gt;</code>. The scope and host are derived
from <code>package.json</code> and <code>NPM_REPO_URL</code> respectively. A global <code>registry=</code> would
push public transitive deps through Verdaccio (which has no uplink) and 404
<code>npm install</code>; a bare <code>_auth=</code> is rejected by modern npm. See
<a href="../93_library_registry/">93 – Library Registry</a>.</p>
<ul>
<li><strong>Ship as-is.</strong> Name and version come from <code>package.json</code>, so the same file
is reused verbatim across every npm package.</li>

View File

@@ -635,6 +635,39 @@
</span>
</a>
<nav class="md-nav" aria-label="Publishing &amp; registry auth">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#endpoints" class="md-nav__link">
<span class="md-ellipsis">
Endpoints
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#npm-scope-the-registry-never-set-it-globally" class="md-nav__link">
<span class="md-ellipsis">
npm: scope the registry, never set it globally
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#verdaccio-storage-permissions" class="md-nav__link">
<span class="md-ellipsis">
Verdaccio storage permissions
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
@@ -839,6 +872,67 @@ so the CI credentials (<code>PIP_USERNAME</code> / <code>PIP_PASSWORD</code>, <c
<code>NPM_PASSWORD</code>) are one and the same account — there is no separate npm user.
Verdaccio requires <strong>bcrypt</strong> htpasswd entries; see
<a href="../90_drone_pipelines/">90 – Drone Pipelines</a> for the CI wiring.</p>
<h3 id="endpoints">Endpoints</h3>
<table>
<thead>
<tr>
<th>Registry</th>
<th>URL</th>
<th>Backend</th>
<th>CI secret</th>
</tr>
</thead>
<tbody>
<tr>
<td>PyPI</td>
<td><code>https://pip.aetoskia.com</code></td>
<td>pypiserver (<code>server-pi:6007</code>)</td>
<td><code>PIP_REPO_URL</code></td>
</tr>
<tr>
<td>npm</td>
<td><code>https://npm.aetoskia.com</code></td>
<td>Verdaccio (<code>server-pi:6008</code>)</td>
<td><code>NPM_REPO_URL</code></td>
</tr>
</tbody>
</table>
<p>Both are exposed through Traefik on the <code>websecure</code> entrypoint as <strong>bare
origins</strong> — no path, no trailing slash. Set the secret values exactly as above.</p>
<h3 id="npm-scope-the-registry-never-set-it-globally">npm: scope the registry, never set it globally</h3>
<p>Verdaccio here has <strong>no uplink / proxy</strong>, so it can only serve <code>@aetoskia/*</code>
packages. A global <code>registry=https://npm.aetoskia.com</code> in <code>.npmrc</code> would route
<em>every</em> dependency — including public transitive deps such as
<code>zod-to-json-schema</code> (pulled in by <code>@modelcontextprotocol/sdk</code>) — through
Verdaccio, which 404s on anything it does not host. Always scope the registry to
the account:</p>
<div class="language-text highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-0-1">1</a></span>
<span class="normal"><a href="#__codelineno-0-2">2</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1"></a>@aetoskia:registry=https://npm.aetoskia.com
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2"></a>//npm.aetoskia.com/:_auth=&lt;base64(user:password)&gt;
</span></code></pre></div></td></tr></table></div>
<p>Public deps then resolve from <code>registry.npmjs.org</code> and only <code>@aetoskia/*</code> hits
the private registry. The auth line must be <strong>scoped to the host</strong>
(<code>//host/:_auth=</code>), not a bare <code>_auth=</code> — modern npm rejects the bare form with
<code>ERR_INVALID_AUTH</code>. The drone template derives both from the package scope and
<code>NPM_REPO_URL</code> automatically; see
<a href="../90_drone_pipelines/">90 – Drone Pipelines</a>.</p>
<blockquote>
<p>The Drone pipeline runs each <code>commands:</code> list item in a <strong>separate shell</strong>, so
shell variables defined in one item do not survive into the next. Keep the
variable definitions and the commands that use them in the same <code>|</code> block.</p>
</blockquote>
<h3 id="verdaccio-storage-permissions">Verdaccio storage permissions</h3>
<p>Verdaccio runs as uid <strong>10001</strong> (<code>verdaccio</code>) inside <code>verdaccio/verdaccio</code>.
The <code>storage</code> directory is bind-mounted from the server
(<code>/mnt/omnissiah-vault/data/verdaccio/storage</code>); if it is owned by <code>root:root</code>
(mode <code>755</code>) the process cannot write to it and every publish fails with a
confusing <code>404 no such package available</code> (<code>NotFoundError</code> from
<code>local-storage</code>). Ensure the directory — and its <code>plugins</code> subdirectory — are
owned by <code>10001:101</code>:</p>
<div class="language-sh highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-1-1">1</a></span>
<span class="normal"><a href="#__codelineno-1-2">2</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1"></a>sudo<span class="w"> </span>chown<span class="w"> </span>-R<span class="w"> </span><span class="m">10001</span>:101<span class="w"> </span>/mnt/omnissiah-vault/data/verdaccio/storage
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2"></a>docker<span class="w"> </span>restart<span class="w"> </span>npm-registry
</span></code></pre></div></td></tr></table></div>
<h2 id="notes">Notes</h2>
<ul>
<li><strong>Hexa</strong> is a full library with its own MCP server on <code>:8008</code>: read

File diff suppressed because one or more lines are too long