fix(pkg-cache): HTTPS-only cache, GPG-verified release RPMs, strict restore
Security review follow-up for the opt-in PKG_CACHE path: - PKG_CACHE must be https:// (http is refused). The cache's certificate comes from a name-constrained internal CA (keys/pkg-cache/ca.crt, valid only for 192.168.1.40 / pkg-cache.*). The CA is trusted only by the rewritten repos (dnf sslcacert bundle, apt per-host CaInfo) and only for the duration of the RUN. - epel-release / remi-release are no longer installed straight from a URL. release-install downloads them over HTTPS, checks the package name, imports the signing keys committed in keys/pkg-cache/ and requires `rpm -K` "signatures OK", then installs with localpkg_gpgcheck=1. This applies with and without the cache. - Rewritten repos get gpgcheck=1. Remi and AlmaLinux also get repo_gpgcheck=1, since both sign repomd.xml. EPEL does not publish a repomd signature, so it relies on end-to-end HTTPS. - restore also deletes files it created and any new repo/apt file that still references the cache, then fails the build if a reference remains under /etc/yum.repos.d, /etc/dnf or /etc/apt. - docs: trust model, replay risk, proposed openssl/httpd/ImageMagick minimum-version gates, TLS registry mirror (BuildKit ca= / dockerd certs.d, no insecure-registries). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b2bec5b3ee
commit
18f65c6d84
10 files changed
+411
-134
No files matched your search
+135
-69
@@ -1,89 +1,128 @@
|
||||
# Internal package + image cache (`PKG_CACHE`)
|
||||
|
||||
Opt-in build support for the LAN package cache **CT130 `pkg-cache` = `192.168.1.40`**
|
||||
(Proxmox node `pve`, storage `shared-storage-tn`). Without `PKG_CACHE`, builds use
|
||||
the upstream repositories exactly as before.
|
||||
Opt-in build support for the LAN package cache **CT130 `pkg-cache` at `https://192.168.1.40`** (Proxmox node `pve`, storage `shared-storage-tn`).
|
||||
|
||||
Why it exists: during the 2026-10-21 PHP security release, builds failed because
|
||||
Remi mirrors lagged hours behind origin, Remi's origin reset or timed out HTTPS
|
||||
under load, and an EPEL mirror had a broken certificate.
|
||||
Without `PKG_CACHE`, builds use the upstream repositories. The one change from before is that the EPEL and Remi release RPMs are now **GPG-verified** against `keys/pkg-cache/` before they are installed.
|
||||
|
||||
Why it exists: during the 2026-10-21 PHP security release, builds failed because:
|
||||
- Remi mirrors lagged hours behind origin;
|
||||
- Remi's origin reset or timed out HTTPS under load;
|
||||
- an EPEL mirror had a broken certificate.
|
||||
|
||||
On that day a build without the cache spent **1031 s** in its first dnf step. Through the cache the same step took **40 s cold and 21 s warm**.
|
||||
|
||||
## What the cache serves
|
||||
|
||||
| URL on the cache | Upstream tiers, tried in order (stale copy last) |
|
||||
| URL (https, pkg-cache CA) | Upstream tiers, tried in order (stale copy last) |
|
||||
|---|---|
|
||||
| `http://192.168.1.40/remi/` | `https://rpms.remirepo.net`, then `http://rpms.remirepo.net` (origin only, no mirrors) |
|
||||
| `http://192.168.1.40/epel/` | `https://dl.fedoraproject.org/pub/epel`, `https://mirror.math.princeton.edu/pub/epel`, `https://mirrors.kernel.org/fedora-epel` |
|
||||
| `http://192.168.1.40/almalinux/` | `https://repo.almalinux.org/almalinux`, `https://mirror.math.princeton.edu/pub/almalinux` |
|
||||
| `http://192.168.1.40/ubuntu/` | `https://archive.ubuntu.com/ubuntu`, `https://us.archive.ubuntu.com/ubuntu` |
|
||||
| `http://192.168.1.40/ubuntu-security/` | `https://security.ubuntu.com/ubuntu`, `https://archive.ubuntu.com/ubuntu` |
|
||||
| `http://192.168.1.40/litespeed/` | `https://rpms.litespeedtech.com`, then `http://rpms.litespeedtech.com` |
|
||||
| `192.168.1.40:5000` (plain http) | Docker Hub pull-through cache (`registry-1.docker.io`) |
|
||||
| `https://192.168.1.40/remi/` | `https://rpms.remirepo.net`, then `http://rpms.remirepo.net` (origin only, no mirrors) |
|
||||
| `https://192.168.1.40/epel/` | `https://dl.fedoraproject.org/pub/epel`, `https://mirror.math.princeton.edu/pub/epel`, `https://mirrors.kernel.org/fedora-epel` |
|
||||
| `https://192.168.1.40/almalinux/` | `https://repo.almalinux.org/almalinux`, `https://mirror.math.princeton.edu/pub/almalinux` |
|
||||
| `https://192.168.1.40/ubuntu/` | `https://archive.ubuntu.com/ubuntu`, `https://us.archive.ubuntu.com/ubuntu` |
|
||||
| `https://192.168.1.40/ubuntu-security/` | `https://security.ubuntu.com/ubuntu`, `https://archive.ubuntu.com/ubuntu` |
|
||||
| `https://192.168.1.40/litespeed/` | `https://rpms.litespeedtech.com`, then `http://rpms.litespeedtech.com` |
|
||||
| `192.168.1.40:5000` (TLS, pkg-cache CA) | Docker Hub pull-through cache (`registry-1.docker.io`) |
|
||||
|
||||
Plain `http://192.168.1.40/` answers **403** to LAN clients.
|
||||
|
||||
Cache rules:
|
||||
- **Metadata** has a 3-minute TTL. After that it is revalidated upstream (`If-Modified-Since`). Metadata here means `repomd.xml*`, repodata files whose names don't start with a hash, apt `dists/` except `by-hash/`, `*-latest*.rpm`, `remi-release-N.rpm`, `RPM-GPG-KEY*` and directory listings.
|
||||
- **Packages** (`*.rpm`, `*.deb`, hash-named repodata, `by-hash/`) are kept for 30 days, and evicted after 60 days without use. The cache is capped at 110 GB (`min_free` 15 GB).
|
||||
- On a connect error, timeout or 5xx, a request falls through to the next tier. Only the **last** tier answers from a stale copy, so metadata goes primary → fallback → stale. A fresher fallback always wins over stale.
|
||||
- `proxy_cache_lock` is on: concurrent misses on one file make a single upstream fetch.
|
||||
- Each response carries `X-Cache-Status` (`HIT`/`MISS`/`REVALIDATED`/`EXPIRED`/`STALE`/`BYPASS`) and `X-Pkgcache-Tier`. On a HIT, the tier header names the location, not the upstream that filled the entry.
|
||||
- **Metadata** has a 3-minute TTL. After that it is revalidated upstream (`If-Modified-Since`). Metadata means:
|
||||
- `repomd.xml*`
|
||||
- repodata files whose names don't start with a hash
|
||||
- apt `dists/`, except `by-hash/`
|
||||
- `*-latest*.rpm` and `remi-release-N.rpm`
|
||||
- `RPM-GPG-KEY*`
|
||||
- directory listings
|
||||
- **Packages** (`*.rpm`, `*.deb`, hash-named repodata, `by-hash/`) are cached for 30 days and evicted after 60 days unused. Total size is capped at 110 GB, with 15 GB kept free.
|
||||
- On a connect error, timeout or 5xx, the request falls through to the next tier. Only the **last** tier may answer from a stale copy, so the order is primary, then fallback, then stale.
|
||||
- `proxy_cache_lock` is on.
|
||||
- Responses carry `X-Cache-Status` and `X-Pkgcache-Tier`.
|
||||
|
||||
## Building with the cache
|
||||
|
||||
```bash
|
||||
docker buildx build --build-arg PKG_CACHE=http://192.168.1.40 --build-arg PHPVER=85 -f Dockerfile .
|
||||
docker buildx build --build-arg PKG_CACHE=https://192.168.1.40 --build-arg PHPVER=85 -f Dockerfile .
|
||||
```
|
||||
|
||||
`scripts/pkg-cache-repos.sh` (copied to `/usr/local/sbin/`) is wired into
|
||||
`Dockerfile`, `Dockerfile.fpm`, `Dockerfile.shared-httpd` and `Dockerfile.shared-ols`:
|
||||
`scripts/pkg-cache-repos.sh` (installed as `/usr/local/sbin/`) and `keys/pkg-cache/` (installed as `/usr/local/share/pkg-cache/`) are wired into `Dockerfile`, `Dockerfile.fpm`, `Dockerfile.shared-httpd` and `Dockerfile.shared-ols`. The script has four modes.
|
||||
|
||||
- `rpm`: rewrites the AlmaLinux, EPEL and Remi `baseurl`s to `<cache>/<prefix>/... <origin>/...`, and comments out that section's mirrorlist/metalink. dnf treats the second URL as a failover, so a dead cache falls back to the origin by itself.
|
||||
- `apt`: rewrites the Ubuntu archive/security and LiteSpeed URIs to the cache. apt has no failover, so CI must probe the cache first (see below).
|
||||
- `release-urls`: the EPEL and Remi release RPM URLs, from the cache when it is set.
|
||||
- `restore`: puts the original repo files back. Every RUN that rewrites ends with `restore`, so **no shipped layer contains the cache address**. Images run on datacenter hosts that cannot reach it.
|
||||
**`rpm`** handles AlmaLinux, EPEL and Remi:
|
||||
- Each `baseurl` becomes `<cache>/<prefix>/... <origin>/...`. dnf fails over to the origin if the cache dies.
|
||||
- That section's mirrorlist/metalink is commented out.
|
||||
- The section gets `sslcacert=` pointing at a bundle of the system CAs plus the pkg-cache CA. The bundle lives in the state dir and is deleted by `restore`.
|
||||
- It also gets `gpgcheck=1`.
|
||||
- AlmaLinux and Remi also get `repo_gpgcheck=1`, because both sign `repomd.xml` (measured 2026-10-21: `repomd.xml.asc` returns 200). EPEL publishes no repomd signature (404), so its metadata integrity rests on HTTPS end to end.
|
||||
|
||||
`Dockerfile.litespeed` and `Dockerfile.lsphp` are **not wired yet**. Their
|
||||
multi-stage `lsphp-apt-upgrade.sh` flow needs two changes first:
|
||||
- a `PKG_CACHE` ARG in each stage;
|
||||
- the madison check accepting the cache URL. It currently requires `*rpms.litespeedtech.com*`, and through the cache `apt-cache madison` shows `http://192.168.1.40/litespeed/debian`.
|
||||
**`apt`** handles Ubuntu archive/security and LiteSpeed:
|
||||
- Their URIs are rewritten to the cache.
|
||||
- `/etc/apt/apt.conf.d/99pkg-cache` sets `Acquire::https::192.168.1.40::CaInfo`, so the CA is trusted for the cache host only.
|
||||
- apt verifies the signed `InRelease`/`Release.gpg` as always. It has no failover, so CI must probe the cache first.
|
||||
|
||||
The PHP gates are unchanged and still apply: `EXPECT_PHP_MIN`,
|
||||
`EXPECT_LSPHP_PKG`/`EXPECT_PHP_VERSION`.
|
||||
**`release-install epel [remi]`**:
|
||||
- Downloads the release RPM over HTTPS, from the cache or from the origin.
|
||||
- Checks the package name.
|
||||
- Imports the key committed in `keys/pkg-cache/`.
|
||||
- Requires `rpm -K` to report `digests signatures OK`.
|
||||
- Installs with `--setopt=localpkg_gpgcheck=1`.
|
||||
|
||||
## Why plain HTTP on the LAN (trade-off)
|
||||
**`restore`**:
|
||||
- Puts rewritten files back.
|
||||
- Deletes files the script created.
|
||||
- Deletes any repo/apt file that **appeared during the RUN** and still references the cache.
|
||||
- Then **fails the build** if any file under `/etc/yum.repos.d`, `/etc/dnf` or `/etc/apt` still mentions the cache.
|
||||
|
||||
- The cache fetches upstream over **HTTPS with certificate verification**. Remi and LiteSpeed also have a plain-http origin fallback, which mirrors what the Dockerfile already did.
|
||||
- Client to cache is plain HTTP. dnf still verifies **package GPG signatures**, and apt verifies the signed `InRelease`/`Release.gpg` plus package hashes. A LAN attacker therefore cannot inject packages. On 2026-10-21 apt measurably refused the cached LiteSpeed index when given the wrong key (`NO_PUBKEY 011AA62DEDA1F085`).
|
||||
- The residual risk is **metadata replay or freeze**: an attacker on the LAN path, or on the plain-http fallback leg, serves an *old* but validly signed `repomd.xml`. That would let a build install old PHP. Remi/EPEL/AlmaLinux do not enable `repo_gpgcheck`, so dnf does not check metadata signatures. The **php.net version gates** cover this for our images: a build fails if it gets PHP older than php.net's current release.
|
||||
- WHP **hosts** have no such gate. They should keep the origin as the second `baseurl` and not rely on the cache for freshness on security-release day. Run `pkgcache-refresh` on the cache instead.
|
||||
- Signing keys must **not** be fetched through the cache. Take them from the base image or directly over HTTPS.
|
||||
- HTTPS to the cache (internal CA or a Let's Encrypt DNS-01 certificate for `pkg-cache.internal.cloud-hosting.io`) would close the LAN leg. It needs a CA in every build image or a public DNS name plus certificate. Not done; Josh to decide.
|
||||
Every RUN that rewrites ends with `restore`, so no shipped layer carries the LAN address. This was verified on the built images: 0 references, no state dir, no apt drop-in.
|
||||
|
||||
`PKG_CACHE` must start with `https://`. `http://` is refused.
|
||||
|
||||
`Dockerfile.litespeed` and `Dockerfile.lsphp` are **not wired yet**. Two things are needed first:
|
||||
- the multi-stage `lsphp-apt-upgrade.sh` flow needs a `PKG_CACHE` ARG in each stage;
|
||||
- its madison check must accept the cache URL. It currently requires `*rpms.litespeedtech.com*`, and via the cache madison shows `https://192.168.1.40/litespeed/debian`.
|
||||
|
||||
## Trust model
|
||||
|
||||
`keys/pkg-cache/ca.crt` is the *pkg-cache internal CA*:
|
||||
- EC P-256, valid to 2036.
|
||||
- **Name-constrained** to IP `192.168.1.40` and DNS `pkg-cache.internal.cloud-hosting.io` / `pkg-cache.geekhome.local`. Even its private key, which exists only on CT130 at `/etc/pkgcache-ca/ca.key`, cannot mint a certificate that clients would accept for any other host.
|
||||
- It is trusted only by the rewritten repos, the apt per-host option and the BuildKit/dockerd registry config. It is never added to a system trust store.
|
||||
|
||||
The server certificate `/etc/pkgcache-ca/server.crt` **expires 2029-10-20**. `pkgcache-stats` prints the expiry. Renew it with the same `openssl x509 -req ... -extfile srv.cnf` steps in `/etc/pkgcache-ca/`.
|
||||
|
||||
Remaining risks:
|
||||
- **Replay of old but validly signed metadata.** Signatures stop tampering, not freshness. The cache's own plain-http tiers for Remi and LiteSpeed are the leg where an internet MITM could replay. The php.net gates (`EXPECT_PHP_MIN`, `EXPECT_LSPHP_PKG`/`EXPECT_PHP_VERSION`) fail an image build that gets old PHP. **Josh to decide** whether to drop those http tiers: you gain replay resistance and lose availability when Remi's HTTPS is down, as it was all day on 2026-10-21.
|
||||
- **Proposed, not implemented:** the same kind of minimum-version gate for other security-relevant packages:
|
||||
- CI resolves the newest `openssl`/`openssl-libs` and `httpd` from AlmaLinux BaseOS/AppStream, and `ImageMagick` from EPEL, **directly from the origin over HTTPS** (e.g. `dnf repoquery --repofrompath=o,https://repo.almalinux.org/almalinux/10/BaseOS/x86_64/os/ --repo=o --latest-limit=1 --qf '%{evr}' openssl-libs`).
|
||||
- It passes the results as `EXPECT_OPENSSL_MIN` / `EXPECT_HTTPD_MIN` / `EXPECT_IMAGEMAGICK_MIN`.
|
||||
- The Dockerfile asserts `rpm -q --qf '%{EVR}'` is not older (`rpm --eval "%{lua: print(rpm.vercmp('A','B'))}"`).
|
||||
|
||||
## CI integration plan (NOT applied — needs Josh)
|
||||
|
||||
1. **Network (blocker).** The ephemeral runners (`eph-pve-*` on `pve` vmbr2 10.99.98.0/24, `eph-pve3-*` on `pve3` vmbr1 10.99.99.0/24) are NATed, and `iptables FORWARD` **drops all RFC1918 destinations**, including 192.168.1.40. A narrow exception is needed on each node, placed before the DROP lines in `/etc/network/interfaces.d/*`:
|
||||
1. **Network (blocker).** The ephemeral runners are NATed, and `iptables FORWARD` **drops all RFC1918 destinations**, including 192.168.1.40:
|
||||
- `eph-pve-*` on `pve`, vmbr2, 10.99.98.0/24;
|
||||
- `eph-pve3-*` on `pve3`, vmbr1, 10.99.99.0/24.
|
||||
|
||||
A narrow exception is needed, placed before the DROP lines:
|
||||
```
|
||||
# pve (/etc/network/interfaces.d/ vmbr2 stanza), before the 192.168.0.0/16 DROP:
|
||||
post-up iptables -A FORWARD -i vmbr2 -o vmbr0 -d 192.168.1.40 -p tcp -m multiport --dports 80,5000 -j ACCEPT
|
||||
post-down iptables -D FORWARD -i vmbr2 -o vmbr0 -d 192.168.1.40 -p tcp -m multiport --dports 80,5000 -j ACCEPT
|
||||
# live, without ifreload:
|
||||
iptables -I FORWARD 1 -i vmbr2 -o vmbr0 -d 192.168.1.40 -p tcp -m multiport --dports 80,5000 -j ACCEPT
|
||||
# pve3: same with vmbr1
|
||||
# pve, /etc/network/interfaces.d/ vmbr2 stanza, BEFORE the 192.168.0.0/16 DROP:
|
||||
post-up iptables -A FORWARD -i vmbr2 -o vmbr0 -d 192.168.1.40 -p tcp -m multiport --dports 443,5000 -j ACCEPT
|
||||
post-down iptables -D FORWARD -i vmbr2 -o vmbr0 -d 192.168.1.40 -p tcp -m multiport --dports 443,5000 -j ACCEPT
|
||||
# live: iptables -I FORWARD 1 -i vmbr2 -o vmbr0 -d 192.168.1.40 -p tcp -m multiport --dports 443,5000 -j ACCEPT
|
||||
# pve3: the same with vmbr1
|
||||
```
|
||||
`gitea-runner.internal.cloud-hosting.io` resolves to 10.10.14.20, which is not on this LAN. Whether it can reach 192.168.1.40 is untested.
|
||||
2. **Probe, then opt in** (no `${{ }}` inside `run:`, per repo rule):
|
||||
`gitea-runner.internal.cloud-hosting.io` resolves to 10.10.14.20, which is not on this LAN. Whether it can reach the cache is untested.
|
||||
2. **Probe with the CA, then opt in.** Keep `${{ }}` out of `run:`. The probe fails when the cache is down **or its certificate is expired or untrusted**, and the build then uses upstream.
|
||||
```yaml
|
||||
- name: Probe package cache
|
||||
id: pkgcache
|
||||
run: |
|
||||
if curl -fsS --max-time 5 -o /dev/null http://192.168.1.40/ ; then
|
||||
echo "url=http://192.168.1.40" >> "$GITHUB_OUTPUT"
|
||||
if curl -fsS --max-time 5 --cacert keys/pkg-cache/ca.crt -o /dev/null https://192.168.1.40/ ; then
|
||||
echo "url=https://192.168.1.40" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "url=" >> "$GITHUB_OUTPUT"; echo "::warning::pkg-cache unreachable - building from upstream"
|
||||
fi
|
||||
# in docker/build-push-action `build-args:` add
|
||||
# PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||
# docker/build-push-action `build-args:` add PKG_CACHE=${{ steps.pkgcache.outputs.url }}
|
||||
```
|
||||
3. **Docker Hub mirror for BuildKit.** The workflows use `docker/setup-buildx-action` (docker-container driver). BuildKit **ignores dockerd's `registry-mirrors`**, so configure the builder instead:
|
||||
3. **Docker Hub mirror for BuildKit**, over TLS and without `insecure`. `setup-buildx-action` uses the docker-container driver, which **ignores dockerd's `registry-mirrors`**. buildx copies the `ca` file into the builder.
|
||||
```yaml
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
@@ -91,34 +130,61 @@ The PHP gates are unchanged and still apply: `EXPECT_PHP_MIN`,
|
||||
[registry."docker.io"]
|
||||
mirrors = ["192.168.1.40:5000"]
|
||||
[registry."192.168.1.40:5000"]
|
||||
http = true
|
||||
ca = ["keys/pkg-cache/ca.crt"]
|
||||
```
|
||||
This exact config was used for the test builds on 192.168.1.193. Only Docker Hub images go through it; `repo.anhonesthost.net/...` is unaffected.
|
||||
4. **dockerd clients** (plain `docker pull`, e.g. the build server or test hosts). Untested, because applying it needs a daemon restart. Add to `/etc/docker/daemon.json` and restart docker:
|
||||
```json
|
||||
{ "registry-mirrors": ["http://192.168.1.40:5000"], "insecure-registries": ["192.168.1.40:5000"] }
|
||||
The exact equivalent (absolute `ca` path) was measured on 192.168.1.193. BuildKit falls back to Docker Hub when the mirror is down; this was measured too, with the registry stopped. The relative `ca` path inside a workflow is **untested**; use `${{ github.workspace }}/keys/pkg-cache/ca.crt` if it doesn't resolve.
|
||||
4. **dockerd clients** (plain `docker pull`). Untested, because it needs a daemon restart. Use TLS, no `insecure-registries`:
|
||||
```
|
||||
install -D -m0644 keys/pkg-cache/ca.crt /etc/docker/certs.d/192.168.1.40:5000/ca.crt
|
||||
# /etc/docker/daemon.json: { "registry-mirrors": ["https://192.168.1.40:5000"] } then restart docker
|
||||
```
|
||||
|
||||
## WHP hosts (plan only)
|
||||
|
||||
The production hosts (whp01/whp02/sdbees) are in the datacenter and **cannot reach 192.168.1.40**. Only LAN hosts such as 192.168.1.148 can. Options for Josh:
|
||||
- (a) a second identical cache CT in the datacenter;
|
||||
The production hosts (whp01, whp02, sdbees) are in the datacenter and **cannot reach 192.168.1.40**. Only LAN hosts can, e.g. 192.168.1.148. Options for Josh:
|
||||
- (a) a second identical cache in the datacenter, with its own name-constrained CA;
|
||||
- (b) expose this one over NetBird;
|
||||
- (c) LAN hosts only.
|
||||
|
||||
For a host that can reach a cache, use the same idea as `pkg-cache-repos.sh rpm`, keeping the origin second so the cache is never a single point of failure:
|
||||
For a host that can reach a cache:
|
||||
- install the CA at `/etc/pki/pkg-cache/ca.crt`;
|
||||
- create a bundle (`cat /etc/pki/tls/certs/ca-bundle.crt /etc/pki/pkg-cache/ca.crt > /etc/pki/pkg-cache/bundle.pem`);
|
||||
- in each repo section, keep the origin as the second URL:
|
||||
|
||||
```ini
|
||||
# /etc/yum.repos.d/remi-safe.repo [remi-safe] (AL9: enterprise/9)
|
||||
baseurl=http://<cache>/remi/enterprise/$releasever_major/safe/$basearch/ https://rpms.remirepo.net/enterprise/$releasever_major/safe/$basearch/
|
||||
[remi-safe]
|
||||
baseurl=https://<cache>/remi/enterprise/$releasever_major/safe/$basearch/ https://rpms.remirepo.net/enterprise/$releasever_major/safe/$basearch/
|
||||
#mirrorlist=...
|
||||
sslcacert=/etc/pki/pkg-cache/bundle.pem
|
||||
gpgcheck=1
|
||||
repo_gpgcheck=1 # Remi + AlmaLinux sign repomd; EPEL does not
|
||||
```
|
||||
|
||||
Hosts have no php.net gate. On a security-release day, run `pkgcache-refresh` on the cache and check `dnf info php-cli` against php.net.
|
||||
|
||||
## Operating the cache (CT130)
|
||||
|
||||
- Config: `/etc/nginx/conf.d/pkgcache-http.conf` (cache zone, maps, log format) and `/etc/nginx/pkgcache/{server-common,meta,pkg}.conf`. `/etc/nginx/sites-available/pkgcache` is **generated** by `/usr/local/sbin/pkgcache-gen-nginx`: edit its `UPSTREAMS` table, then run `pkgcache-gen-nginx > /etc/nginx/sites-available/pkgcache && nginx -t && systemctl reload nginx`.
|
||||
- Registry: `/etc/docker/registry/config.yml` (Debian `docker-registry` 2.8.3, the same code as `registry:2`), service `docker-registry`.
|
||||
- Data: nginx in `/srv/cache/nginx`, registry in `/srv/cache/registry` (150 GB mount point `mp0`, `backup=0`).
|
||||
- **Security-release day:** run `pkgcache-refresh` to re-fetch every cached metadata entry now (or `pkgcache-refresh remi` for one prefix; `--list` shows them; `--purge` deletes them instead). A refresh bypasses the cache from localhost only and replaces each entry in place. If upstream is down, the old copy stays. Without it, metadata is at most 3 minutes old anyway.
|
||||
- Monitoring: `pkgcache-stats` shows disk, hit ratio, fallback-tier use, STALE count and upstream errors. Logs are in `/var/log/nginx/pkgcache.access.log` (with `cache=`, `tier=`, `up=` fields) and `journalctl -u docker-registry`. Suggested Uptime Kuma checks: `http://192.168.1.40/` and `http://192.168.1.40:5000/v2/`.
|
||||
- Docker Hub: anonymous pulls are rate-limited per egress IP, and every client now shares this cache's IP. To raise the limit, add `username`/`password` (a Hub access token) under `proxy:`.
|
||||
- **nginx config.**
|
||||
- `/etc/nginx/conf.d/pkgcache-http.conf` holds the cache zone, maps and log format.
|
||||
- `/etc/nginx/pkgcache/{server-common,meta,pkg}.conf` hold the shared settings.
|
||||
- `/etc/nginx/sites-available/pkgcache` is **generated** by `/usr/local/sbin/pkgcache-gen-nginx`. Edit the `UPSTREAMS` table, then run `pkgcache-gen-nginx > /etc/nginx/sites-available/pkgcache && nginx -t && systemctl reload nginx`.
|
||||
- Listeners: :443 TLS for the LAN, `127.0.0.1:80` for local tools, and :80 for the LAN answers 403.
|
||||
- **Registry.** Config is `/etc/docker/registry/config.yml` (Debian `docker-registry` 2.8.3, the same code as `registry:2`, with TLS). Service `docker-registry`.
|
||||
- **TLS.** Everything is in `/etc/pkgcache-ca/`: `ca.key` (0600), `ca.crt`, `server.key`, `server-chain.crt`, and the `ca.cnf`/`srv.cnf` openssl configs.
|
||||
- **Data.** nginx uses `/srv/cache/nginx`, the registry uses `/srv/cache/registry`. Both live on the 150 GB mount point `mp0` (`backup=0`).
|
||||
- **Security-release day.** `pkgcache-refresh` re-fetches every cached metadata entry right away. Variants:
|
||||
- `pkgcache-refresh remi` refreshes one prefix;
|
||||
- `--list` lists the entries;
|
||||
- `--purge` deletes them instead.
|
||||
|
||||
A refresh bypasses the cache from localhost only and replaces each entry in place. If upstream is down, the old copy stays. Metadata is at most 3 minutes old anyway.
|
||||
- **Monitoring.** `pkgcache-stats` shows:
|
||||
- disk usage;
|
||||
- hit ratio;
|
||||
- fallback-tier use;
|
||||
- the STALE count;
|
||||
- upstream errors;
|
||||
- certificate expiry.
|
||||
|
||||
Logs are `/var/log/nginx/pkgcache.access.log` (fields `cache=`, `tier=`, `up=`) and `journalctl -u docker-registry`. Suggested Uptime Kuma checks are `https://192.168.1.40/` and `https://192.168.1.40:5000/v2/`, with certificate checking against the CA.
|
||||
- **Docker Hub rate limits.** Anonymous pulls are limited per egress IP, and all clients share it. Add `username`/`password` (a Hub access token) under `proxy:` if needed.
|
||||
Reference in new issue
Block a user