Ctrl K
ComunidadGuías…

docs/RELEASING.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Releasing

How a version goes from this repo to downloadable artifacts on
<https://github.com/7sh1d0w7x/linux-doctor/releases>.

## Release checklist

Patch = fixes only, minor = new checks or flags. At 0.x anything may change
(SemVer §4), but a fixes-only release still reads as a patch.

```bash
node scripts/bump-version.mjs X.Y.Z          # 1. bump every manifest, the AppStream release entry, and generated docs
# 2. CHANGELOG.md: move [Unreleased] into `## [X.Y.Z] - YYYY-MM-DD`
npm test                                     # 3. full suite
git commit -am "chore: release X.Y.Z"        # 4. commit, push, wait for green CI on main
git tag -s vX.Y.Z -m "vX.Y.Z"                # 5. signed tag
git push origin vX.Y.Z                       # 6. triggers release.yml
```

7. Watch the release workflow: it attaches the tarball, AppImage, `.deb`, `.rpm`,
   `latest.json` and the checksums, and extracts the notes from `CHANGELOG.md`
   verbatim (not auto-generated).
8. **Publish to npm** (manual, next section). Skipping this leaves
   `npx linux-doctor` on an old version: only 0.2.0, 0.2.1 and 0.6.0 ever
   reached the registry.
9. Bump downstream: OBS (new tarball + spec, see packaging/README.md). For AUR
   (once registration reopens): put the tarball's real sha256 into
   `packaging/aur/PKGBUILD` and regenerate the pair with
   `makepkg --printsrcinfo > packaging/aur/.SRCINFO`, since both ship SKIP until
   then. AppImageHub needs nothing: it tracks channels, not versions.
10. Verify what users will get:

```bash
gh release view vX.Y.Z                      # assets

# The signature check takes the pubkey, the .sig and the file it signs — the
# script cannot guess them, and calling it with no arguments only prints usage.
PUBKEY=$(node -e 'console.log(require("./src-tauri/tauri.conf.json").plugins.updater.pubkey)')
node scripts/verify-updater-signature.mjs \
  "$PUBKEY" <AppImage>.sig <AppImage>       # AppImage signature vs the pubkey

npx linux-doctor@latest --version           # the registry serves X.Y.Z
```

11. Smoke test one real install path (npx or the AppImage), then announce. A
    patch gets release notes; aggregators (Hacker News, Reddit) wait for a
    release that adds something.

## What CI builds automatically

Pushing a tag `vX.Y.Z` triggers `.github/workflows/release.yml`:

| Job | Runs on | Produces |
|---|---|---|
| **cli** | ubuntu-latest | `linux-doctor-X.Y.Z.tgz` (npm pack, after `npm test`) + `SHA256SUMS` |
| **gui** | ubuntu-22.04 | `linux-doctor-X.Y.Z-x86_64.AppImage` (+ `.sig`), `Linux.Doctor_X.Y.Z_amd64.deb`, `Linux.Doctor-X.Y.Z-1.x86_64.rpm`, `latest.json`, `SHA256SUMS-bundles.txt` |

Both jobs attach their files to the same GitHub Release. Release notes are
extracted verbatim from `CHANGELOG.md` via `awk` (body_path), not auto-generated. Each asset gets a Sigstore attestation via `actions/attest`. The gui job builds on 22.04 on
purpose: the AppImage links against an older glibc, so it runs on most
distributions.

## Cut a release (with signed tag + SHA256SUMS)

```bash
# 0. one-time GPG setup (see /RELEASING.md)
# 1. bump all manifests atomically + regenerate docs:
node scripts/bump-version.mjs 0.4.0
# 2. curate CHANGELOG.md: move [Unreleased] -> ## [0.4.0] - YYYY-MM-DD
npm run goldens:update   # if output changed
npm test                 # must be green (600+ tests)
npm pack --dry-run | grep src-gui/index.html
node bin/doctor.js --self-test && node bin/doctor.js --json | jq .checksRun

# Real-browser dashboard smoke (not in PR CI — needs a cached Chromium +
# playwright-core). Catches JS/console errors a vm test cannot:
node bin/doctor.js --web & sleep 5
node scripts/check-dashboard.mjs http://127.0.0.1:43901/
kill %1


git commit -am "chore: release 0.4.0"
git push origin main              # wait for CI green (Node 20/22/24 + Fedora + Rust)
git tag -s v0.4.0 -m "v0.4.0" && git tag -v v0.4.0
git push origin v0.4.0            # triggers release.yml -> tgz + AppImage/deb/rpm + SHA256SUMS + attestations
# 3. verify Release: SHA256SUMS, CHANGELOG body, --version matches tag
```

## Publish to npm (manual)

The `npm publish` step in `release.yml` is disabled (`if: false`): npm now
stages new publishes for malware scanning (5–15 min) and needs auth the tag
workflow does not carry. Publish from the release commit instead:

```bash
git checkout v0.6.0        # the tag, so the tarball matches the release
npm publish                # needs an npm token with 2FA
npm view linux-doctor version   # confirm it went live (a few minutes later)
```

`linux-doctor@0.6.0` is live, so `npx linux-doctor` and
`npm i -g linux-doctor` resolve to it. Only publish a version that does not
exist yet — the registry rejects a second upload of the same version (409),
and a first upload sits in staging for a few minutes before it appears.

## Build the GUI locally (optional)

Needs WebKitGTK/GTK3 dev libraries, which immutable systems (Bazzite,
Silverblue) do not expose directly. Use a toolbox container — inside it you
have passwordless sudo:

```bash
toolbox -y create -c ldbuild
toolbox run -c ldbuild -- bash -lc '
  sudo dnf install -y nodejs npm rust cargo \
    webkit2gtk4.1-devel gtk3-devel librsvg2-devel file patchelf openssl-devel'
cd <repo>
toolbox run -c ldbuild -- bash -lc 'npm ci && npm run build:gui && npx tauri build --bundles deb'
```

Artifacts land in `src-tauri/target/release/bundle/deb/`.

Two gotchas learned the hard way:

- **Run what you built inside the same container** (`toolbox run -c ldbuild
  -- ./src-tauri/target/release/linux-doctor` with
  `WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/1000`). A binary
  compiled against the container's WebKitGTK crashes on the host's older
  libs (`free(): corrupted unsorted chunks`).
- **AppImage bundling may fail on bleeding-edge Fedora containers**: the
  `strip` shipped inside linuxdeploy rejects modern `.relr.dyn` ELF sections
  found in current Fedora libraries (`unknown type [0x13]`). Exporting
  `APPIMAGE_EXTRACT_AND_RUN=1` is required regardless (no FUSE), but does
  not fix strip. Practical split: build **deb locally**, let **CI produce
  the AppImage** on ubuntu-22.04.

## Node runtime resolution

The desktop shell runs the Node CLI under the hood. It picks the interpreter
in this order: `$LINUX_DOCTOR_NODE`, `<resources>/runtime/node` (a runtime
bundled into the packages by `node scripts/fetch-node-runtime.mjs` — run
before every `tauri build`; release.yml does it automatically), then `node`
from PATH. Since 0.5.x the .deb/.AppImage/.rpm ship the Node 22 LTS binary,
so end users need nothing on their PATH; the runtime dir is gitignored and
must be refetched after a clean checkout.

## Asset naming convention

```
linux-doctor-0.3.0-x86_64.AppImage    # primary download (catalog convention)
Linux.Doctor_0.3.0_amd64.deb          # Debian/Ubuntu
Linux.Doctor-0.3.0-1.x86_64.rpm       # Fedora/RHEL/openSUSE
linux-doctor-0.3.0.tgz                # npm CLI tarball
```

The AppImage uses the `<repo>-<version>-<arch>.AppImage` name so catalogs
(auto-discovering it from the GitHub Releases) work. `release.yml` also
repacks it to make `AppRun.wrapped` world-executable (0755) — linuxdeploy
ships it 0770, which breaks sandboxes that mount the AppImage as root and drop
to a user (Firejail, used by AppImageHub) — and re-signs it for the updater.

## Release notes (minimum)

```
🩺 Linux Doctor v0.3.0

Download `linux-doctor-0.3.0-x86_64.AppImage`, then:
  chmod +x linux-doctor-*-x86_64.AppImage && ./linux-doctor-*-x86_64.AppImage

CLI users: npx github:7sh1d0w7x/linux-doctor  (Node ≥ 20)
Read-only diagnostics by default — nothing changes unless you opt in with `--fix --yes`.
```

## Auto-update (desktop app)

The Tauri app checks GitHub Releases for a newer **signed** build and, with
the user's consent, installs it and restarts. The check runs ~12s after
startup and from the tray's **Check for updates** item. `LINUX_DOCTOR_NO_UPDATE=1`
disables it (dev/tests).

- Endpoint: `https://github.com/7sh1d0w7x/linux-doctor/releases/latest/download/latest.json`
  (configured in `src-tauri/tauri.conf.json` → `plugins.updater.endpoints`).
- The manifest `latest.json` is assembled by `scripts/make-latest-json.mjs`
  from the signed AppImage (`*.AppImage` + `*.AppImage.sig`) during the release
  job — Tauri's CLI does not write it (that is `tauri-action`'s job, which this
  repo does not use).
- The release then runs `scripts/verify-updater-signature.mjs`, which checks the
  AppImage's signature and the `latest.json` signature against the **public key
  embedded in the app** — the same thing the updater does at install time. A
  release cannot ship an artifact the updater would reject.
- **Linux auto-update targets the AppImage.** `.deb`/`.rpm` users update
  through their package manager.

### Signing key (one-time, then a CI secret)

```bash
npx tauri signer generate -w ~/.tauri/linux-doctor.key   # keep the private key safe
```

1. Put the printed **public** key into `src-tauri/tauri.conf.json` →
   `plugins.updater.pubkey`.
2. Store the **private** key as the repository secret
   `TAURI_SIGNING_PRIVATE_KEY` (and `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`, empty
   for a passwordless key). **Never commit it.**
3. The release job fails fast if the secret is missing — a release without it
   would produce artifacts no installed app can verify.

**If the private key is lost, existing installs can no longer be updated** by
a new key (the public key is baked into each build). Generate a new pair,
update `pubkey`, and ship one release users install manually; from then on
auto-update works again.