# npm.nech.pl — private npm registry (Verdaccio) on erable

Hosts the `@nech/*` packages (the generated API clients, `@nech/api`). Public
deps proxy through to npmjs, so a project using `@nech/api` still resolves
`react` etc. normally. Same shape as `api.nech.pl`: a loopback container behind
the nginx TLS edge.

DNS `npm.nech.pl` → erable is set. erable = `pln@nech.pl`, Docker, nginx in
`/etc/nginx/sites-enabled/`, passwordless sudo. Run as `pln`.

> **Status: DEPLOYED 2026-06-29.** `npm.nech.pl` is live (LE TLS, loopback
> Verdaccio 6.7.4 on `:4873`), publisher `pln` seeded + signups locked, and
> `@nech/api@0.1.0` is published. Creds stashed on erable `~/.config/nechapi/`
> (`verdaccio.pln.{pw,token}`, 0600). Three gotchas the bare runbook below missed,
> now folded in: (1) set `listen: 0.0.0.0:4873` in config or the container binds
> localhost-INSIDE and the `-p` map is dead; (2) `chown -R 10001:65533
> ~/srv/verdaccio` for the bind-mount (verdaccio uid); (3) erable's old OpenSSL
> rejects `TLSv1.3` — `ssl_protocols TLSv1.2;` only. **Reads are auth-gated too**
> (`@nech/*` access=$authenticated, consistent with the bearer-gated
> `/openapi.json`), so consumers need an npm read token, not just the registry line.

## 1. Verdaccio container (loopback :4873)

```bash
mkdir -p ~/srv/verdaccio/{storage,conf}
# config — auth required to publish, @nech is local, everything else proxies npmjs
cat > ~/srv/verdaccio/conf/config.yaml <<'YAML'
storage: /verdaccio/storage
auth:
  htpasswd:
    file: /verdaccio/storage/htpasswd
    max_users: -1            # -1 after seeding = no open self-registration
uplinks:
  npmjs:
    url: https://registry.npmjs.org/
packages:
  '@nech/*':
    access: $authenticated
    publish: $authenticated
    unpublish: $authenticated
  '**':
    access: $authenticated
    publish: $authenticated
    proxy: npmjs
server:
  keepAliveTimeout: 60
log: { type: stdout, format: pretty, level: info }
YAML

docker run -d --name verdaccio --restart unless-stopped \
  -p 127.0.0.1:4873:4873 \
  -v ~/srv/verdaccio/storage:/verdaccio/storage \
  -v ~/srv/verdaccio/conf:/verdaccio/conf \
  verdaccio/verdaccio:6
```

(If Docker is the old 19.03 that needs it for other images, Verdaccio's node
image is fine without `seccomp=unconfined` — it's not the glibc/clone3 case.)

## 2. nginx vhost + TLS

```bash
sudo certbot certonly --webroot -w /var/www/challenges -d npm.nech.pl
sudo tee /etc/nginx/sites-available/npm.nech.pl.conf >/dev/null <<'NGINX'
server {
    listen 80; listen [::]:80;
    server_name npm.nech.pl;
    include includes/acme-challenge;
    location / { return 301 https://$host$request_uri; }
}
server {
    listen 443 ssl http2; listen [::]:443 ssl http2;
    server_name npm.nech.pl;
    ssl_certificate     /etc/letsencrypt/live/npm.nech.pl/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/npm.nech.pl/privkey.pem;
    ssl_protocols TLSv1.2;
    client_max_body_size 50m;          # tarballs
    location / {
        proxy_pass http://127.0.0.1:4873;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
NGINX
sudo ln -sf /etc/nginx/sites-available/npm.nech.pl.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```

## 3. Seed the publisher user, then lock signups

```bash
npm adduser --registry https://npm.nech.pl     # create pln
# then set max_users: -1 (already above) and restart so no one else can self-register
docker restart verdaccio
```

## 4. Publish @nech/api

```bash
cd armada/api/clients/nech-api
npm login --registry https://npm.nech.pl
npm publish                                     # prepublishOnly runs the build
```

Consumers (hexa): `echo '@nech:registry=https://npm.nech.pl' >> .npmrc && npm i @nech/api`.
